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

Circular (or cyclic) imports in Python

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

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


去年在comp.lang.python上有一个非常好的讨论。它很彻底地回答了你的问题。

Imports are pretty straightforward really. Just remember the following:

'import' and 'from xxx import yyy' are executable statements. They execute
when the running program reaches that line.

If a module is not in sys.modules, then an import creates the new module
entry in sys.modules and then executes the code in the module. It does not
return control to the calling module until the execution has completed.

If a module does exist in sys.modules then an import simply returns that
module whether or not it has completed executing. That is the reason why
cyclic imports may return modules which appear to be partly empty.

Finally, the executing script runs in a module named __main__, importing
the script under its own name will create a new module unrelated to
__main__.

Take that lot together and you shouldn't get any surprises when importing
modules.


如果你在bar内做import foo,在foo内做import bar,效果会很好。当任何东西实际运行时,两个模块都将被完全加载,并且彼此都有引用。

问题是,当你改为使用from foo import abcfrom bar import xyz时。因为现在每个模块都要求在导入之前已经导入另一个模块(以便我们导入的名称存在)。


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

考虑以下文件:

A.PY:

1
2
3
4
5
print"a in"
import sys
print"b imported: %s" % ("b" in sys.modules, )
import b
print"a out"

B.PY:

1
2
3
4
print"b in"
import a
print"b out"
x = 3

如果执行a.py,将得到以下信息:

1
2
3
4
5
6
7
8
9
$ 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中。

如果在模块初始化期间尝试从a访问b.x,您将得到一个AttributeError

a.py后面加一行:

1
print b.x

然后,输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ 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之后发生。


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

1
2
3
def dostuff(self):
     from foo import bar
     ...

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

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

如果你积极希望进口商品占上风,那么这些ImportErrors几乎总是可以避免的:

考虑此循环导入:

应用程序A

1
2
3
4
5
6
7
8
9
# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

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

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

应用程序B

1
2
3
4
5
6
7
8
9
# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

从大卫比兹利优秀的谈话模块和包装:活,让死!-pycon 2015,1:54:00,这里有一种处理python循环导入的方法:

1
2
3
4
5
try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

这将尝试导入SimplifiedImageSerializer,如果ImportError被提升,因为它已经被导入,它将从importcache中提取它。

附:你必须用大卫·比兹利的声音读这整篇文章。


我有一个让我震惊的例子!

P.Py

1
2
3
4
import bar

class gX(object):
    g = 10

巴利

1
2
3
from foo import gX

o = gX()

Me.Py

1
2
3
4
import foo
import bar

print"all done"

在命令行:$python main.py

1
2
3
4
5
6
7
8
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


我完全同意皮索纳的回答。但是我偶然发现了一些代码,这些代码有循环导入的缺陷,并且在尝试添加单元测试时引起了问题。因此,要在不更改任何内容的情况下快速修补它,您可以通过执行动态导入来解决问题。

1
2
3
4
5
6
7
8
9
10
# 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")

同样,这不是一个永久性的修复,但可以帮助那些希望在不更改过多代码的情况下修复导入错误的人。

干杯!


模块ApPy:

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

模块BY

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

运行"模块A"将输出:

1
2
3
4
5
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>

它输出这3行,而由于循环导入,它应该输出无穷大。在运行"模块A"时,逐行发生的情况如下:

  • 第一行是import b。所以它将访问模块B
  • 模块B的第一行是import a。所以它将访问模块A
  • 模块A的第一行是import b,但请注意,这一行将不再被执行,因为python中的每个文件只执行一次导入行,无论何时何地执行它都无关紧要。因此,它将转到下一行并打印"This is from module a"
  • 从模块B访问完整个模块A后,我们仍在模块B,所以下一行将打印"This is from module b"
  • 模块B线路完全执行。因此,我们将回到模块A,从那里开始模块B。
  • 导入B行已执行,将不再执行。下一行将打印"This is from module a",程序将完成。

  • 我用下面的方法解决了这个问题,它工作得很好,没有任何错误。考虑两个文件a.pyb.py

    我在a.py上加了这个,它起作用了。

    1
    2
    if __name__ =="__main__":
            main ()

    A.PY:

    1
    2
    3
    4
    5
    6
    7
    8
    import b
    y = 2
    def main():
        print ("a out")
        print (b.x)

    if __name__ =="__main__":
        main ()

    B.PY:

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

    我得到的输出是

    1
    2
    3
    >>> b out
    >>> a out
    >>> 5

    循环导入可能会混淆,因为导入有两个功能:

  • 它执行导入的模块代码
  • 将导入的模块添加到导入模块全局符号表
  • 前者只执行一次,而后者在每个import语句中执行。循环导入在导入模块使用部分执行代码的导入模块时创建情况。因此,它将看不到在import语句之后创建的对象。下面的代码示例演示了它。

    循环进口并不是不惜一切代价避免的最终祸害。在一些框架中,比如flask,它们是非常自然的,调整代码以消除它们并不能使代码更好。

    Me.Py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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.通过

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

    A.Py

    1
    2
    3
    4
    5
    6
    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输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    2
    3
    4
    5
    def MandrillEmailOrderSerializer():
    from sastaticketpk.apps.flights.api.v1.serializers import MandrillEmailOrderSerializer
    return MandrillEmailOrderSerializer

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

    好吧,我想我有一个很酷的解决方案。假设你有EDOCX1文件(3)和EDOCX1文件(1)。您在文件b中有一个def或一个class要在模块a中使用,但是您还有其他的东西,要么是defclass文件,要么是a文件中您在定义或文件b中的类中需要的变量。您可以做的是,在文件a的底部,在调用文件b所需的文件a中的函数或类之后,在从文件b调用文件a所需的函数或类之前,例如import b。那么,这里是关键部分,在文件b中所有需要文件def或文件class中的a的定义或类中,你说from a import CLASS

    这是因为您可以导入文件b,而不需要python执行文件b中的任何导入语句,因此可以避免任何循环导入。

    例如:

    文件A:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class A(object):

         def __init__(self, name):

             self.name = name

    CLASS = A("me")

    import b

    go = B(6)

    go.dostuff

    文件B:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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."

    沃伊拉