我在玩循环导入时发现了一些意想不到的事情。我在同一目录中有两个文件:
a.py
import b
print("hello from a")
b.py
import a
print("hello from b")
运行python3 a.py
和python3 b.py
不会导致循环导入相关错误。我知道第一个导入的模块是以__main__
的名义导入的,但我仍然不理解这种行为。例如,运行python3 a.py
或python -m a
会产生以下输出:
hi from a
hi from b
hi from a
查看在回答我自己的问题之前,我没有正确使用print(sys.modules.keys())
的输出,我可以看到在检查时已经以某种方式导入了两个模块,即使在将sys
模块作为其中一个模块中的第一件事导入时也是如此。sys.modules
。
如果循环导入的模块都不是它仍然会发生,但仅当您实际使用循环导入模块之一的内容时,才会出现可见错误。__main__
模块,则不会发生这种情况。我的 Python 版本Python 3.6.3
Ubuntu 17.10
。
请参阅我自己的答案以进行澄清。
我的问题的答案
我已经找到了答案。我将尝试勾勒出一个解释:
执行python3 a.py
将文件a.py
中的模块导入为__main__
:
-
模块
__main__
中的import b
:-
模块
b
中的import a
-> 将文件a.py
中的模块作为a
导入 -
模块
a
中的import b
-> 没有任何反应,已经导入了该模块 -
a.py
print('hello from a')
(执行模块a
) -
模块中的
import a
b
已完成
-
-
b.py
中的print('hello from b')
(执行模块b
) - 模块中的
import b
__main__
已完成 print('hello from a')
a.py
(执行模块__main__
)
问题是本身没有循环导入错误。一个模块只导入一次,之后,同一模块的其他导入可以被视为无操作。
此操作可以看作是向sys.modules
字典中添加一个与导入模块名称对应的键,然后在执行时设置与该键关联的模块对象的属性。因此,如果该键已存在于字典中(在同一模块的第二次导入中),则在第二次导入时不会发生任何反应。上面已经导入的意思是已经存在于sys.modules
字典中。这反映了Python的过程性质(最初是用C实现的),以及Python中的任何内容都是对象的事实。
潜伏的问题
为了显示与循环导入相关的问题仍然存在的事实,让我们向模块b
添加一个函数,并尝试从模块a
使用它。
a.py
import b
b.f()
b.py
import a
def f():
print('hello from b.f()')
现在执行python a.py
__main__
导入文件a.py
中的模块:
模块
__main__
中的import b
:模块
b
中的import a
-> 将文件a.py
中的模块作为a
导入模块
a
中的import b
-> 没有任何反应,已经导入了该模块b.f()
->AttributeError: module 'b' has no attribute 'f'
注意:行b.f()
可以进一步简化为b.f
,并且仍然会出现错误。这是因为b.f()
首先访问模块对象b
的属性f
,它恰好是一个函数对象,然后尝试调用它。我想再次指出Python的面向对象本质。
from ... import ...
声明
有趣的是,使用from ... import ...
形式会产生另一个错误,即使原因相同:
a.py
from b import f
f()
b.py
import a
def f():
printf('hello from b.f()')
执行python a.py
将文件a.py
中的模块导入为__main__
:
模块
__main__
中的from b import f
实际上导入整个模块(将其添加到sys.modules
并执行其主体),但仅绑定当前模块命名空间中的名称f
:模块
b
中的import a
-> 将文件a.py
中的模块作为a
导入from b import f
模块a
->ImportError: cannot import name f
中(因为第一次执行from b import f
没有看到模块b
中f
函数对象的定义)
在最后一种情况下,from ... import ...
本身会失败并显示错误,因为解释器更早地知道您正在尝试访问该模块中不存在的内容。将其与第一个AttributeError
进行比较,其中程序在尝试访问属性f
(在表达式b.f
中)之前没有看到任何问题。
主模块中代码的双重执行问题
当从另一个模块导入用于启动程序的文件(首先导入__main__
)中的模块时,该模块中的代码将执行两次,并且该模块执行中的任何副作用也将发生两次。这就是为什么不建议在其他模块中再次导入程序的主模块的原因。
使用sys.modules
来证实我上面的结论
我将展示如何检查sys.modules
的内容可以澄清这个问题:
a.py
import sys
assert '__main__' in sys.modules.keys()
print(f'{__name__}:')
print('ta imported:', 'a' in sys.modules.keys())
print('tb imported:', 'b' in sys.modules.keys())
import b
b.f()
b.py
import sys
assert '__main__' in sys.modules.keys()
print(f'{__name__}:')
print('ta imported:', 'a' in sys.modules.keys())
print('tb imported:', 'b' in sys.modules.keys())
import a
assert False # Control flow never gets here
def f():
print('hi from b.f()')
python3 a.py
的输出:
__main__:
a imported: False
b imported: False
b:
a imported: False
b imported: True
a:
a imported: True
b imported: True
Traceback (most recent call last):
File "a.py", line 8, in <module>
import b
File "/home/andrei/PycharmProjects/untitled/b.py", line 8, in <module>
import a
File "/home/andrei/PycharmProjects/untitled/a.py", line 10, in <module>
b.f()
AttributeError: module 'b' has no attribute 'f'