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.
如果你在
问题是,当你改为使用
循环导入将终止,但在模块初始化期间,请注意不要使用循环导入的模块。
考虑以下文件:
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时(在第二个
如果在模块初始化期间尝试从
在
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' |
这是因为模块是在导入时执行的,在访问
正如其他答案所描述的,这种模式在Python中是可以接受的:
1 2 3 | def dostuff(self): from foo import bar ... |
这将避免在其他模块导入文件时执行import语句。只有存在逻辑循环依赖关系时,此操作才会失败。
大多数循环导入实际上不是逻辑循环导入,而是导致
如果你积极希望进口商品占上风,那么这些
考虑此循环导入:
应用程序A1 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 2 3 4 5 | try: from images.serializers import SimplifiedImageSerializer except ImportError: import sys SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer'] |
这将尝试导入
附:你必须用大卫·比兹利的声音读这整篇文章。
我有一个让我震惊的例子!
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"时,逐行发生的情况如下:
我用下面的方法解决了这个问题,它工作得很好,没有任何错误。考虑两个文件
我在
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)。您在文件
这是因为您可以导入文件
例如:
文件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." |
沃伊拉