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


351

如果两个模块相互导入会怎样?

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



1
也作为参考,似乎在python 3.5(可能以后)上允许循环导入,但在3.4(可能下面)上不允许循环导入。
查理·帕克

4
我正在使用python 3.7.2,由于循环依赖关系,仍然出现运行时错误。
理查德·怀特黑德

Answers:


280

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

导入确实非常简单。只要记住以下几点:

'import'和'from xxx import yyy'是可执行语句。它们在运行的程序到达该行时执行。

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

如果sys.modules中确实存在某个模块,则无论导入是否完成执行,导入都会简单地返回该模块。这就是循环导入可能返回部分为空的模块的原因。

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

放在一起,导入模块时就不会感到惊讶。


13
@meawoppl能否请您扩展此评论?它们的变化程度如何?
Dan Schien '16

3
到目前为止,python3中唯一对循环导入的引用“有什么新功能?” 页数在3.5之一。它说:“现在支持涉及相对进口的循环进口”。@meawoppl您是否找到了这些页面中未列出的其他内容?
zezollo '16

4
他们是防守。3.0-3.4不支持。或者至少成功的语义是不同的。这是我发现的概要,没有提到3.5的更改。 gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl '16

请您在“最后,正在执行的脚本在名为main的模块中运行,以其自己的名称导入脚本将创建一个与main无关的新模块”上进行扩展。因此,可以说该文件是a.py,当它运行为主要切入点,itsbthe 现在如果它就像从进口一些变量的代码。然后,将相同的文件“ a.py”加载到sys模块表中吗?那么,这是否意味着如果它说出print语句,那么它将运行两次?一次用于主文件,一次在导入时一次?
可变的

这个答案
已有

296

如果在import foo内部barimport bar内部进行操作foo,它将正常工作。到实际运行任何东西时,两个模块都将完全加载,并且将相互引用。

问题是当您改为from foo import abc和时from bar import xyz。因为现在每个模块都需要先导入另一个模块(这样才能导入我们要导入的名称),然后才能导入它。


27
看来,from foo import *from bar import *也将正常工作。
Akavall

1
使用a.py/b.py检查对以上帖子的编辑。他没有使用from x import y,但仍然收到循环导入错误
Greg Ennis 2014年

2
这并非完全正确。就像import * from一样,如果您试图访问循环导入中的顶级元素,那么在脚本完成其运行之前,您将遇到相同的问题。例如,如果要在一个包中将另一个包全局设置为一个包,并且它们都相互包含。我这样做是为了为基类中的对象创建一个草率的工厂,其中该对象可以是许多子类之一,并且使用代码不需要知道它实际上是在创建哪个。
AaronM '16

3
@Akavall不是。那只会导入import执行该语句时可用的名称。因此它不会出错,但是您可能无法获得所有期望的变量。
augurar

3
请注意,如果您执行 from foo import *from bar import *foo则在中执行的所有操作都处于的初始化阶段bar,并且bar尚未定义中的实际功能……
Martian2049

100

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

考虑以下文件:

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


14
这在很大程度上解释了问题,但是解决方案如何?我们如何正确导入和打印x?上面的其他解决方案对我不起作用
穆罕默德(Mehmet)

我认为,如果您使用__name__而不是,此答案将大有裨益'a'。一开始,我完全困惑为什么文件要执行两次。
贝尔吉

30

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

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

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

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

ImportErrors如果您确实希望进口货物居于首位,则几乎可以避免这些情况

考虑以下循环导入:

应用程式A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

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

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

应用程式B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

大卫·比兹利(David Beazleys)精彩演讲模块和软件包:活着,让自己死!-PyCon 20151:54:00这是在python中处理循环导入的一种方法:

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

这会尝试导入SimplifiedImageSerializer,如果ImportError引发,因为已经被导入,它将从importcache中将其拉出。

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


9
如果已经导入模块,则不会引发ImportError。可以根据需要多次导入模块,即“ import a; import a;”。还可以
尤拉斯(Yuras)'16

9

我这里有一个让我印象深刻的例子!

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

2
您是如何解决的?我试图了解循环导入以解决自己的问题,该问题看起来与您正在做的事情非常相似...
c089 2010年

12
错误...我认为我已经解决了这个难以置信的丑陋问题。{{{如果不是sys.modules中的'foo.bar':从foo导入栏其他:bar = sys.modules ['foo.bar']}}}就我个人而言,我认为循环导入是对错误代码的巨大警告设计...
c089 2010年

5
@ C089,或者你可以只移动import barfoo.py到最后
warvariuc

5
如果barfoo两者都必须使用gX的“干净”的解决方案是把gX另一个模块,并同时拥有foobar进口该模块。(就没有隐藏的语义依赖性而言,这是最干净的。)
Tim Wilder

2
蒂姆有一个很好的观点。基本上是因为在foo中bar找不到gX。循环导入本身就可以,但是只是gX在导入时未定义。
Martian2049年

9

模块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",程序将完成。

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")

同样,这不是永久性的修复,但是可以帮助想要修复导入错误而无需更改太多代码的人。

干杯!


3

这里有很多很好的答案。尽管通常可以快速解决问题,但有些解决方案比其他解决方案更具有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')

将允许save_result从A中删除方法,从而允许从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原理时。


2

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

  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"

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

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

0

好的,我想我有一个很酷的解决方案。假设您有file a和file b。你有一个def或一个class文件b要在模块来使用a,但你有别的东西,无论是一个defclass或者从文件变量a您在文件中定义或类需要b。您可以做的是,在文件底部a,在调用文件a中所需的文件中b的函数或类之后,但是在从文件b中调用所需的文件中调用函数或类之前a,说import b 然后,这是关键部分,在文件b中所有需要the defor classfrom file 的定义或类中a(叫它CLASS),你说from a import CLASS

之所以可行,是因为您可以导入文件b而无需Python在file中执行任何import语句b,因此可以避免任何循环导入。

例如:

档案a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

文件b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."


from a import CLASS实际上并不会跳过执行a.py中的所有代码。这就是实际发生的情况:(1)a.py中的所有代码都作为特殊模块“ __main__”运行。(2)在处import b运行b.py中的顶级代码(定义类B),然后控制权返回到“ __main__”。(3)“ __main__”最终将控制权交给go.dostuff()。(4)当dostuff()出现时import a,它将再次运行a.py中的所有代码,这次作为模块“ a”运行;然后从新模块“ a”导入CLASS对象。因此,实际上,如果您import a在b.py中的任何地方使用它,也将同样有效。
马提亚斯·弗里普
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.