循环导入本身不一定是问题。只有循环依赖。您可以通过一些简单的实验来了解循环导入是如何解决的:
在mymod1.py
import sys
print("1. from mymod1:", [x for x in sys.modules if x.startswith("mymod")])
import mymod2
print("2. from mymod1:", [x for x in sys.modules if x.startswith("mymod")])
在mymod2.py
import sys
print("1. from mymod2:", [x for x in sys.modules if x.startswith("mymod")])
import mymod3
print("2. from mymod2:", [x for x in sys.modules if x.startswith("mymod")])
在mymod3.py
import sys
print("1. from mymod3:", [x for x in sys.modules if x.startswith("mymod")])
import mymod1
print("2. from mymod3:", [x for x in sys.modules if x.startswith("mymod")])
现在我们有一个循环mymod1 -> mymod2 -> mymod3 -> mymod1。输入 REPL 并观察会发生什么:
>>> import mymod1
1. from mymod1: ['mymod1'] # before mymod1 imported mymod2. note mymod1 is already there!
1. from mymod2: ['mymod1', 'mymod2'] # in mymod2 now, before importing mymod3
1. from mymod3: ['mymod1', 'mymod2', 'mymod3'] # before mymod3 imports mymod1
2. from mymod3: ['mymod1', 'mymod2', 'mymod3'] # mymod3 exit
2. from mymod2: ['mymod1', 'mymod2', 'mymod3'] # mymod2 exit
2. from mymod1: ['mymod1', 'mymod3', 'mymod2'] # mymod1 exit
这里的关键见解是模块实例本身在它完成执行之前已经存在于sys.modules 中。这意味着它可以再次导入并返回现有对象,而无需再次执行所有模块级代码。
包的子模块中的组件具有相互依赖关系是很自然的。当模块范围的代码开始实际做诸如尝试连接到数据库之类的事情时,问题大多会出现,因此请尽量避免直接在模块级别编写任何脚本。
循环导入错误的原因是在模块完成初始化之前需要模块命名空间中的某些内容。在这种情况下,模块本身存在,但您尝试访问的名称可能还不存在。
# mymod.py
import sys
print("var" in vars(sys.modules["mymod"]))
var = "I'm a name in mymod namespace"
print("var" in vars(sys.modules["mymod"]))
导入mymod 会打印False,然后是True,模块的命名空间本身仍在变化,因为导入正在执行过程中。
精明的读者可能已经注意到 mymod2 和 mymod3 在输出中交换了位置:
2. from mymod2: ['mymod1', 'mymod2', 'mymod3']
2. from mymod1: ['mymod1', 'mymod3', 'mymod2']
|_____ wtf? _____|
这其实绝非偶然!作为加载模块的最后一步,导入机制将实际模块从sys.modules 中拉出。如果您在 REPL 中再次检查,mymod1 现在将是最后一个。
>>> import mymod1
...
>>> [x for x in sys.modules if x.startswith("mymod")]
['mymod3', 'mymod2', 'mymod1']
我不会描述导入系统为什么会这样做,因为它与问题并不真正相关,但有兴趣了解原因的用户应该查看this mailing list post from Guido。