在 Python 中,大部分人都应该都遇到过循环导入的问题。
循环导入是指两个文件各自尝试导入另一个文件(模块),当一个模块没有完全初始化时会导致失败。解决这种情况的最好方法是将代码分层组织,这样导入的关系就会自然地朝着一个方向流动。但是更加简单的方式只需更改使用的 import
语句的样式即可。
# one.py
from two import func_two
def func_one():
func_two()
# two.py
from one import func_one
def do_work():
func_one()
def func_two():
print("Hello, world!")
# main.py
from two import do_work
do_work()
如果我们运行 main.py , 会得到如下的报错信息:
Traceback (most recent call last):
File "/mnt/data/python/main.py", line 2, in <module>
from two import do_work
File "/mnt/data/python/two.py", line 2, in <module>
from one import func_one
File "/mnt/data/python/one.py", line 2, in <module>
from two import func_two
ImportError: cannot import name 'func_two' from partially initialized module 'two' (most likely due to a circular import)
当 Python 导入一个模块时,它会逐行执行该文件。文件中的每个全局变量(包括函数和类)都成为正在构造的模块对象的一个属性。
在 two.py
中,我们在第2行从 one.py
导入一个函数。此时,已经有了one
和two
模块,但是它们还没有属性,因为还没有定义任何东西。模块two
最终会包含 do_work
和 func_two
函数,但是还没有执行到这些 def
语句,所以它们暂时不存在。
与函数调用一样,当 import 语句运行时,它开始执行导入的文件,直到导入完成后才返回当前文件。
当导入one.py
时,它的第2行尝试从two
模块导入func_two
。正如我们刚才所说的,two
模块已经存在,但是还没有定义func_two
,这就导致了错误。
解决这个问题的方法也很简单:我们不需要从模块导入具体的函数名称,而是可以导入整个模块,然后使用导入的模块中的函数,如下所示:
# one.py
import two # was: from two import func_two
def func_one():
two.func_two() # was: func_two()
# two.py
import one # was: from one import func_one
def do_work():
one.func_one() # was: func_one()
def func_two():
print("Hello, world!")
# main.py
from two import do_work
do_work()
这次就可以正常运行 main.py
了。
这次能够正常运行是因为 two.py
在第2行导入one
模块,然后 one.py
在第2行导入two
模块,这样是可以正常导入的,因为这两个模块都存在。就像上面提到的那样,它们此刻是空的,但是现在我们没有导入某个具体的函数名称。在完成所有导入之后,one
和two
模块就都会包含了他们的内部的函数名称,我们可以使用模块名来引用其中的函数了。
这里的关键点是 from two import func_two
语句在导入期间尝试查找 func_two
(在它存在之前)。通过使用import two
将函数名称查找延迟到函数体内,可以让所有模块完全初始化,避免循环导入的错误。
正如在开头提到的,修复循环导入的最佳方法是对代码结构进行优化,这样模块就不会相互依赖。但这样在代码比较复杂时的改动会非常大,使用上面提到的方法可以暂时解决问题。