Python 循环导入意外行为

Python cyclic import unexpected behavior

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

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 版本是 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'