Python 循环导入意外行为
Python cyclic import unexpected behavior
我在使用循环导入时发现了一些意想不到的东西。我在同一目录中有两个文件:
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 版本是 Ubuntu 17.10
上的 Python 3.6.3
。
它仍然会发生,但只有当您实际上从循环导入的模块之一中使用某些东西时,才会出现可见错误。
请参阅我自己的回答以进行说明。
我的问题的答案
我找到了答案。我将尝试草拟一个解释:
执行 python3 a.py
将文件 a.py
中的模块导入为 __main__
:
import b
模块 __main__
:
import a
in module b
-> 将文件 a.py
中的模块导入为 a
import b
in module a
-> 没有任何反应,already imported that module
print('hello from a')
in a.py
(执行模块a
)
import a
模块 b
完成
print('hello from b')
in b.py
(执行模块b
)
import b
模块 __main__
完成
print('hello from a')
in a.py
(执行模块 __main__
)
问题是本身没有循环导入错误。一个模块只导入一次,之后再导入同一个模块可以看作是空操作。
这个操作可以看作是在 sys.modules
字典中添加一个与导入模块名称相对应的键,然后在执行时在与该键关联的模块对象上设置属性。因此,如果键已经存在于字典中(在同一模块的第二次导入中),则第二次导入时不会发生任何事情。上面的 already imported 表示已经存在于 sys.modules
词典中。这反映了 Python 的过程性质(最初在 C 中实现)以及 Python 中的任何东西都是对象这一事实。
潜伏的问题
为了证明与循环导入相关的问题仍然存在,让我们向模块 b
添加一个函数并尝试从模块 a
.[=96= 使用它]
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
in module b
-> 将文件 a.py
中的模块导入为 a
import b
in module a
-> 没有任何反应,already imported that module
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__
:
from b import f
模块 __main__
实际上 导入 整个模块(将其添加到 sys.modules
并执行其主体), 但仅绑定当前模块命名空间中的名称 f
:
import a
in module b
-> 将文件 a.py
中的模块导入为 a
from b import f
in module a
-> ImportError: cannot import name f
(因为第一次执行from b import f
没有看到函数的定义模块 b
)
中的对象 f
在最后一种情况下,from ... import ...
本身会因错误而失败,因为解释器及早知道您正在尝试访问该模块中不存在的内容。将它与第一个 AttributeError
进行比较,其中程序在尝试访问属性 f
(在表达式 b.f
中)之前没有发现任何问题。
main模块中代码的双重执行问题
当从另一个模块导入用于启动程序的文件中的模块(首先导入为 __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'
我在使用循环导入时发现了一些意想不到的东西。我在同一目录中有两个文件:
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 版本是 Ubuntu 17.10
上的 Python 3.6.3
。
请参阅我自己的回答以进行说明。
我的问题的答案
我找到了答案。我将尝试草拟一个解释:
执行 python3 a.py
将文件 a.py
中的模块导入为 __main__
:
import b
模块__main__
:import a
in moduleb
-> 将文件a.py
中的模块导入为a
import b
in modulea
-> 没有任何反应,already imported that moduleprint('hello from a')
ina.py
(执行模块a
)import a
模块b
完成
print('hello from b')
inb.py
(执行模块b
)import b
模块__main__
完成print('hello from a')
ina.py
(执行模块__main__
)
问题是本身没有循环导入错误。一个模块只导入一次,之后再导入同一个模块可以看作是空操作。
这个操作可以看作是在 sys.modules
字典中添加一个与导入模块名称相对应的键,然后在执行时在与该键关联的模块对象上设置属性。因此,如果键已经存在于字典中(在同一模块的第二次导入中),则第二次导入时不会发生任何事情。上面的 already imported 表示已经存在于 sys.modules
词典中。这反映了 Python 的过程性质(最初在 C 中实现)以及 Python 中的任何东西都是对象这一事实。
潜伏的问题
为了证明与循环导入相关的问题仍然存在,让我们向模块 b
添加一个函数并尝试从模块 a
.[=96= 使用它]
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
in moduleb
-> 将文件a.py
中的模块导入为a
import b
in modulea
-> 没有任何反应,already imported that moduleb.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__
:
from b import f
模块__main__
实际上 导入 整个模块(将其添加到sys.modules
并执行其主体), 但仅绑定当前模块命名空间中的名称f
:import a
in moduleb
-> 将文件a.py
中的模块导入为a
from b import f
in modulea
->ImportError: cannot import name f
(因为第一次执行from b import f
没有看到函数的定义模块b
) 中的对象
f
在最后一种情况下,from ... import ...
本身会因错误而失败,因为解释器及早知道您正在尝试访问该模块中不存在的内容。将它与第一个 AttributeError
进行比较,其中程序在尝试访问属性 f
(在表达式 b.f
中)之前没有发现任何问题。
main模块中代码的双重执行问题
当从另一个模块导入用于启动程序的文件中的模块(首先导入为 __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'