在Python中,当两个模块试图相互导入时会发生什么?更一般地说,如果多个模块试图在一个循环中导入会发生什么?
另见我能做什么关于“ImportError:不能导入名称X”或“AttributeError:…”(很可能是由于循环导入)”?关于可能导致的常见问题,以及如何重写代码以避免此类导入的建议。参见为什么循环导入看起来在调用堆栈中更上一层,但随后在更下一层引发ImportError ?有关问题发生的原因和方式的技术细节。
在Python中,当两个模块试图相互导入时会发生什么?更一般地说,如果多个模块试图在一个循环中导入会发生什么?
另见我能做什么关于“ImportError:不能导入名称X”或“AttributeError:…”(很可能是由于循环导入)”?关于可能导致的常见问题,以及如何重写代码以避免此类导入的建议。参见为什么循环导入看起来在调用堆栈中更上一层,但随后在更下一层引发ImportError ?有关问题发生的原因和方式的技术细节。
去年在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:
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时(在第二个ain中),Python解释器不会再次导入b,因为它已经存在于模块dict中。
如果你试图在模块初始化期间从a访问b.x,你会得到一个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退出后发生。
如果你导入foo(在bar.py内部)和导入bar(在foo.py内部),它会工作得很好。在实际运行任何东西时,两个模块都将完全加载,并将相互引用。
问题是当你做from foo import abc(在bar.py内)和from bar import xyz(在foo.py内)时。因为现在每个模块都需要另一个模块已经被导入(以便导入的名称存在),然后才能导入它。
这里有个例子让我震惊!
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
Ok, I think I have a pretty cool solution. Let's say you have file a and file b. You have a def or a class in file b that you want to use in module a, but you have something else, either a def, class, or variable from file a that you need in your definition or class in file b. What you can do is, at the bottom of file a, after calling the function or class in file a that is needed in file b, but before calling the function or class from file b that you need for file a, say import b Then, and here is the key part, in all of the definitions or classes in file b that need the def or class from file a (let's call it CLASS), you say from a import CLASS
这是可行的,因为您可以导入文件b,而无需Python执行文件b中的任何导入语句,因此您可以避免任何循环导入。
例如:
文件:
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."
喉咙痛。
正如其他答案所描述的,这种模式在python中是可以接受的:
def dostuff(self):
from foo import bar
...
这将避免在文件被其他模块导入时执行import语句。只有当存在逻辑循环依赖时,这才会失败。
大多数循环导入实际上不是逻辑循环导入,而是会引发ImportError错误,这是因为import()在调用时计算整个文件的顶级语句的方式。
如果你确实想要你的导入在顶部,这些ImportErrors几乎总是可以避免的:
考虑这个循环导入:
应用一个
# 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 2015, 1:54:00,这里是一个处理python循环导入的方法:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
它尝试导入SimplifiedImageSerializer,如果ImportError被引发,因为它已经被导入,它将从importcache中拉出它。
PS:你必须用David Beazley的声音来阅读整篇文章。
我完全同意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")
同样,这不是一个永久性的修复,但可以帮助那些希望在不修改太多代码的情况下修复导入错误的人。
干杯!
循环导入可能令人困惑,因为导入做了两件事:
它执行导入的模块代码 将导入的模块添加到导入模块全局符号表中
前者只执行一次,而后者则在每个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
模块a.py:
import b
print("This is from module a")
模块b.py
import a
print("This is from module b")
运行“Module a”将输出:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
它输出了这3行,而由于循环导入,它应该输出不定式。 这里列出了运行“模块a”时逐行发生的事情:
The first line is import b. so it will visit module b The first line at module b is import a. so it will visit module a The first line at module a is import b but note that this line won't be executed again anymore, because every file in python execute an import line just for once, it does not matter where or when it is executed. so it will pass to the next line and print "This is from module a". After finish visiting whole module a from module b, we are still at module b. so the next line will print "This is from module b" Module b lines are executed completely. so we will go back to module a where we started module b. import b line have been executed already and won't be executed again. the next line will print "This is from module a" and program will be finished.
我用下面的方法解决了这个问题,没有任何错误。 考虑两个文件a.py和b.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
这里有很多很棒的答案。虽然通常有快速解决问题的解决方案,其中一些比其他的更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):
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原则。
假设您正在运行一个名为request.py的测试python文件 在request.py中,您写入
import request
所以这也很可能是一个循环导入。
解决方案:
只需将测试文件更改为另一个名称,例如aaa.py,而不是request.py。
不要使用其他库已经使用过的名称。
令我惊讶的是,还没有人提到由类型提示引起的循环导入。 如果只有由于类型提示才有循环导入,则可以以干净的方式避免它们。
考虑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.py导入了exception.py,反之亦然,通过Foo和SpecificException。
Because Foo is only required in exceptions.py during type checking, we can safely make its import conditional using the TYPE_CHECKING constant from the typing module. The constant is only True during type checking, which allows us to conditionally import Foo and thereby avoid the circular import error. Something to note is that by doing so, Foo is not declared in exceptions.py at runtime, which leads to a NameError. To avoid that, we add from __future__ import annotations which transforms all type annotations in the module to strings.
因此,我们得到以下Python 3.7+的代码:
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 becomes 'Foo' because of the future import
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
在Python 3.6中,future import不存在,所以Foo必须是一个字符串:
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 has to be a string
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
在Python 3.5及以下版本中,类型提示功能还不存在。 在Python的未来版本中,注解特性可能会成为强制性的,之后就不再需要导入了。这将发生在哪个版本尚未确定。
这个答案是基于Stefaan Lippens的另一个解决方案,它将你从Python的循环导入洞中挖出来。
bar.py
print('going to import foo')
from foo import printx
foo.py
print('trying to import bar')
import bar
def printx():
print('x')
$ python bar.py
going to import foo
trying to import bar
going to import foo
Traceback (most recent call last):
File "bar.py", line 2, in <module>
from foo import printx
File "/home/suhail/Desktop/temp/circularimport/foo.py", line 2, in <module>
import bar
File "/home/suhail/Desktop/temp/circularimport/bar.py", line 2, in <module>
from foo import printx
ImportError: cannot import name 'printx' from partially initialized module 'foo' (most likely due to a circular import) (/home/suhail/Desktop/temp/circularimport/foo.py)