Python循环导入意外行为

时间:2018-02-09 07:29:02

标签: python-import python-3.6 circular-dependency cyclic-reference

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

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.3上的Ubuntu 17.10 它仍然会发生,但只有当您从其中一个循环导入的模块中实际使用某些内容时才会出现明显的错误。

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

1 个答案:

答案 0 :(得分:0)

我的问题的答案

我发现了答案。我将尝试草拟一个解释:

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

    模块import b中的
  • __main__

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

    • 模块import b中的
    • a - >没有任何事情发生,已导入该模块

    • {li>

      print('hello from a') a.py(执行模块a

      模块import a中的
    • b已完成

  • {li>

    print('hello from b') b.py(执行模块b

    模块import b中的
  • __main__已完成
  • {li} 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'