Python中的循环(或循环)导入

时间:2009-04-13 16:07:07

标签: python circular-dependency cyclic-reference

如果两个模块相互导入会发生什么?

为了概括这个问题,Python中的循环导入怎么样?

14 个答案:

答案 0 :(得分:245)

去年comp.lang.python对此进行了非常好的讨论。它非常彻底地回答了你的问题。

  

进口非常简单。请记住以下内容:

     

'import'和'from xxx import yyy'是可执行语句。他们执行   当正在运行的程序到达那一行时。

     

如果模块不在sys.modules中,则导入会创建新模块   在sys.modules中输入,然后执行模块中的代码。它不是   将控制权返回给调用模块,直到执行完成。

     

如果sys.modules中存在模块,则导入只返回该模块   模块是否已完成执行。这就是原因   循环导入可能会返回看似部分为空的模块。

     

最后,执行脚本在名为__main__的模块中运行,导入   脚本以其自己的名称将创建一个与之无关的新模块   __main __。

     

将这些东西放在一起,导入时不应该有任何意外   模块。

答案 1 :(得分:245)

如果你import foo内的barimport bar内的foo,它会正常工作。当任何实际运行时,两个模块将完全加载并且将相互引用。

问题在于您执行的是from foo import abcfrom bar import xyz。因为现在每个模块都需要导入其他模块(以便我们导入的名称存在)才能导入。

答案 2 :(得分:95)

循环导入终止,但在模块初始化期间需要注意不要使用循环导入的模块。

考虑以下文件:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

如果您执行a.py,您将获得以下内容:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

在第二次导入b.py时(在第二个a in中),Python解释器不会再次导入b,因为它已存在于模块dict中。

如果您在模块初始化期间尝试从b.x访问a,则会获得AttributeError

将以下行附加到a.py

print b.x

然后,输出是:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

这是因为模块是在导入时执行的,当访问b.x时,行x = 3尚未执行,这只会在b out之后执行。

答案 3 :(得分:25)

正如其他答案描述的那样,这种模式在python中是可以接受的:

def dostuff(self):
     from foo import bar
     ...

当其他模块导入文件时,这将避免执行import语句。只有存在逻辑循环依赖关系时,才会失败。

大多数循环导入实际上不是逻辑循环导入,而是引发ImportError错误,因为import()在调用时评估整个文件的顶级语句的方式。

如果您确实希望将导入设置在最顶层,则几乎总能避免使用这些ImportErrors

考虑这个循环导入:

App A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

App B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

来自David Beazleys的优秀演讲Modules and Packages: Live and Let Die! - PyCon 20151:54:00,这是一种处理python循环导入的方法:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

这会尝试导入SimplifiedImageSerializer,如果引发ImportError,因为它已经导入,它会从导入缓存中提取它。

PS:你必须以David Beazley的声音阅读整篇文章。

答案 4 :(得分:8)

我在这里得到了一个令我印象深刻的例子!

<强> foo.py

import bar

class gX(object):
    g = 10

<强> bar.py

from foo import gX

o = gX()

<强> main.py

import foo
import bar

print "all done"

在命令行: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

答案 5 :(得分:4)

我完全同意pythoneer的答案。但我偶然发现了一些代码,这些代码在循环导入时存在缺陷,并且在尝试添加单元测试时会出现问题。因此,要快速修补它而不更改所有内容,您可以通过动态导入来解决问题。

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

同样,这不是一个永久修复,但可以帮助那些想要修复导入错误但又不会改变太多代码的人。

干杯!

答案 6 :(得分:3)

模块a.py:

import b
print("This is from module a")

模块b.py

import a
print("This is from module b")

运行“模块a”将输出:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

它输出这3行,而由于循环导入而应该输出无限。 下面列出了在运行“模块a”时一行一行地发生的情况:

  1. 第一行是import b。因此它将访问模块b
  2. 模块b的第一行是import a。因此它将访问模块a
  3. 模块a的第一行为import b,但 请注意,此行将不再执行 ,因为python中的每个文件都执行导入行仅执行一次,无论何时何地执行都无关紧要。因此它将传递到下一行并打印"This is from module a"
  4. 从模块b访问完整个模块a后,我们仍在模块b处。因此下一行将显示"This is from module b"
  5. 模块b行已完全执行。因此,我们将返回到模块b所在的模块a。
  6. import b行已被执行,将不再执行。下一行将显示"This is from module a",程序将完成。

答案 7 :(得分:1)

循环导入可能会造成混淆,因为导入有两件事:

  1. 它执行导入的模块代码
  2. 将导入的模块添加到导入模块全局符号表中

前者仅执行一次,而后者在每个import语句中执行一次。当导入模块使用部分执行代码的已导入模块时,循环导入会产生情况。因此,它将不会看到import语句之后创建的对象。下面的代码示例对此进行了演示。

循环进口并不是不惜一切代价避免的最终弊端。在诸如Flask之类的某些框架中,它们是很自然的,调整您的代码以消除它们并不能使代码变得更好。

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

带注释的python main.py输出

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

答案 8 :(得分:1)

我通过以下方式解决了该问题,并且工作正常,没有任何错误。 考虑两个文件a.pyb.py

我将此添加到了a.py中,并且可以正常工作。

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

我得到的输出是

>>> b out 
>>> a out 
>>> 5

答案 9 :(得分:1)

令我惊讶的是,还没有人提到由类型提示引起的循环导入。
如果你有循环导入 only 作为类型提示的结果,它们可以以干净的方式避免。

考虑使用另一个文件中的异常的 main.py

from src.exceptions import SpecificException

class Foo:
    def __init__(self, attrib: int):
        self.attrib = attrib

raise SpecificException(Foo(5))

还有专用的异常类 exceptions.py

from src.main import Foo

class SpecificException(Exception):
    def __init__(self, cause: Foo):
        self.cause = cause

    def __str__(self):
        return f'Expected 3 but got {self.cause.attrib}.'

ImportError 通过 main.pyexception.py 导入 Foo,反之亦然。

因为只有在类型检查期间 SpecificException 中需要 Foo,所以我们可以安全地使用 typing 模块中的 exceptions.py 常量使其导入有条件。在类型检查时常量只有TYPE_CHECKING,这允许我们有条件地导入True,从而避免循环导入错误。
在 Python 3.6 中,使用前向引用:

Foo

在 Python 3.7+ 中,注释的延迟评估(在 PEP 563 中引入)允许使用“正常”类型而不是前向引用:

from typing import TYPE_CHECKING
if TYPE_CHECKING:  # Only imports the below statements during type checking
   ​from src.main import Foo

class SpecificException(Exception):
   ​def __init__(self, cause: 'Foo'):  # The quotes make Foo a forward reference
       ​self.cause = cause

   ​def __str__(self):
       ​return f'Expected 3 but got {self.cause.attrib}.'

在 Python 3.10+ 中,from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: # Only imports the below statements during type checking ​from src.main import Foo class SpecificException(Exception): ​def __init__(self, cause: Foo): # Foo can be used in type hints without issue ​self.cause = cause ​def __str__(self): ​return f'Expected 3 but got {self.cause.attrib}.' 默认处于活动状态,因此可以省略。

此答案基于 Stefaan Lippens 的 Yet another solution to dig you out of a circular import hole in Python

答案 10 :(得分:0)

这里有很多很好的答案。尽管通常可以快速解决此问题,但有些解决方案比其他解决方案更具有Python风格,但如果您愿意进行一些重构,那么另一种方法是分析代码的组织,并尝试消除循环依赖。例如,您可能会发现自己拥有:

文件a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

文件b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

在这种情况下,只需将一个静态方法移至单独的文件,例如c.py

文件c.py

def save_result(result):
    print('save the result')

将允许从A中删除save_result方法,从而允许从b中的a中删除A的导入:

重构文件a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

重构文件b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

总而言之,如果您有一个报告静态方法的工具(例如pylint或PyCharm),则仅在它们上抛出一个staticmethod装饰符可能不是使警告静音的最佳方法。即使该方法似乎与该类相关,最好还是将其分开,尤其是当您有几个紧密相关的模块可能需要相同的功能并且打算实践DRY原理时。

答案 11 :(得分:0)

假设您正在运行名为request.py的测试python文件 在request.py中,您编写

import request

所以这也很可能是循环导入。

解决方案 只需将测试文件更改为aaa.py以外的其他名称,例如request.py

答案 12 :(得分:-1)

这可能是另一种对我有用的解决方案。

def MandrillEmailOrderSerializer():
from sastaticketpk.apps.flights.api.v1.serializers import MandrillEmailOrderSerializer
return MandrillEmailOrderSerializer

email_data = MandrillEmailOrderSerializer()(order.booking).data

答案 13 :(得分:-2)

好的,我觉得我有一个非常酷的解决方案。 我们假设您有文件a和文件b。 您要在模块def中使用文件class中的ba,但您还有其他内容,def,{文件class中定义或类所需的文件a中的{1}}或变量。 在文件b中调用文件a中需要的函数或类之后,但在从文件调用函数或类之前,您可以做的是文件a需要b,说b 然后,这里是关键部分,在文件a中需要文件import b中的bdef的所有定义或类中}(让我们称之为class),你说a

这是有效的,因为您可以导入文件CLASS,而不会执行文件from a import CLASS中的任何导入语句,因此您无法进行任何循环导入。

例如:

档案a:

b

文件b:

b

瞧。