Python 循环导入意外行为



我在玩循环导入时发现了一些意想不到的事情。我在同一目录中有两个文件:

a.py

import b
print("hello from a")

b.py

import a
print("hello from b")

运行python3 a.pypython3 b.py不会导致循环导入相关错误。我知道第一个导入的模块是以__main__的名义导入的,但我仍然不理解这种行为。例如,运行python3 a.pypython -m a会产生以下输出:

hi from a
hi from b
hi from a

查看print(sys.modules.keys())的输出,我可以看到在检查时已经以某种方式导入了两个模块,即使在将sys模块作为其中一个模块中的第一件事导入时也是如此。在回答我自己的问题之前,我没有正确使用sys.modules

如果循环导入的模块都不是__main__模块,则不会发生这种情况。我的 Python 版本Python 3.6.3Ubuntu 17.10它仍然会发生,但仅当您实际使用循环导入模块之一的内容时,才会出现可见错误。

请参阅我自己的答案以进行澄清。

我的问题的答案

我已经找到了答案。我将尝试勾勒出一个解释:

执行python3 a.py将文件a.py中的模块导入为__main__

  • 模块__main__中的import b

    • 模块b中的import a-> 将文件a.py中的模块作为a导入

    • 模块a中的import b-> 没有任何反应,已经导入了该模块

    • a.pyprint('hello from a')(执行模块a)

    • 模块中的import ab已完成

  • 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没有看到模块bf函数对象的定义)

在最后一种情况下,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'

最新更新