我在玩循环导入时发现了一些意想不到的东西。我在同一目录中有两个文件:
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
。
请参阅我自己的答案进行澄清。
答案 0 :(得分:0)
我发现了答案。我将尝试草拟一个解释:
执行python3 a.py
将文件a.py
中的模块导入__main__
:
import b
中的 __main__
:
import a
中的 b
- >将文件a.py
中的模块导入a
import b
中的 a
- >没有任何事情发生,已导入该模块
print('hello from a')
a.py
(执行模块a
)
import a
中的 b
已完成
print('hello from b')
b.py
(执行模块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
将文件a.py
中的模块导入为__main__
:
import b
中的 __main__
:
import a
中的 b
- >将文件a.py
中的模块导入a
import b
中的 a
- >没有任何事情发生,已导入该模块
b.f()
- > AttributeError: module 'b' has no attribute 'f'
注意:行b.f()
可以进一步简化为b.f
,但仍会出现错误。这是因为b.f()
首先访问模块对象f
的属性b
,它恰好是一个函数对象,然后尝试调用它。我想再次指出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__
:
from b import f
中的 __main__
实际导入整个模块(将其添加到sys.modules
并执行其正文),但只绑定名称当前模块命名空间中的f
:
import a
中的 b
- >将文件a.py
中的模块导入a
from b import f
中的 a
- > ImportError: cannot import name f
(因为第一次执行from b import f
没有看到模块f
中函数对象b
的定义)
在最后一种情况下,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'