了解 Python 导入和循环依赖的行为
Understanding behavior of Python imports and circular dependencies
注意:这是关于导入 模块 而不是 类,这些模块的函数,所以我不认为它是鬃毛 "ImportError: cannot import name" 结果的副本,至少我还没有找到匹配的结果。
我知道按名称从模块导入 类 或函数可能会导致问题,因为如果存在循环依赖,模块本身可能还没有完全初始化,但这里不是这种情况。
为了重现这个问题,创建三个循环依赖它的模块。
首先创建一个包:
$ mkdir pkg
$ touch pkg/__init__.py
然后创建pkg/a.py,内容为:
from __future__ import print_function
from __future__ import absolute_import
from . import b
def A(x):
print('I am A, x={}.'.format(x))
b.B(x + 1)
def Z(x):
print('I am Z, x={}. I\'m done now!'.format(x))
和pkg/b.py,内容为:
from __future__ import print_function
from __future__ import absolute_import
from . import c
def B(x):
print('I am B, x={}.'.format(x))
c.C(x * 2)
和pkg/c.py,内容为:
from __future__ import print_function
from __future__ import absolute_import
from . import a
def C(x):
print('I am C, x={}.'.format(x))
a.Z(x ** 2)
还有一个 main.py(在顶级目录中)调用它们:
from __future__ import print_function
from __future__ import absolute_import
from pkg import a
if __name__ == '__main__':
a.A(5)
我预计循环依赖不会有问题,因为在导入期间没有对每个模块中项目的引用(即没有从模块 b 或 c 引用 a.A,除了c.C).
体内的调用
而且,确实,运行 这个 python3 工作得很好:
$ python3 main.py
I am A, x=5.
I am B, x=6.
I am C, x=12.
I am Z, x=144. I'm done now!
(这是 Debian Stretch 上的 Python 3.5.3,供记录。)
但是对于 python2 (Python 2.7.13),它并没有真正起作用,它抱怨循环依赖...
$ python main.py
Traceback (most recent call last):
File "main.py", line 5, in <module>
from pkg import a
File "/tmp/circular/pkg/a.py", line 5, in <module>
from . import b
File "/tmp/circular/pkg/b.py", line 5, in <module>
from . import c
File "/tmp/circular/pkg/c.py", line 5, in <module>
from . import a
ImportError: cannot import name a
所以我的问题是:
为什么我 运行 陷入循环依赖问题,如果我不是从我的模块导入或引用特定的 类 或函数,只是模块本身?
为什么这只发生在 Python 2 上? (在 Python 3 中参考 PEP、代码、发行说明或有关修复此问题的文章将不胜感激。)
有什么方法可以避免Python 2 中的这个问题,同时又不破坏模块的循环依赖?我相信并非所有循环依赖都会导致此问题(即使在 Python 2 中),所以我想知道哪些情况是安全的,哪些情况不是...
我不确定 Python 3 是如何解决这个问题的,但我的经验告诉 Python 2 确实无法解决问题。解决问题的正确方法是:
- 注意不要在你的代码中引入这个
- 在你需要的地方导入内部函数
我个人更喜欢后者
关于为什么,Python 中的模块系统在成功加载模块之前不会标记它。所以在你的 "import a",Python 不会知道它已经加载 "a" 直到所有依赖加载,"b" 和 "c" 在它经历整个 "a.py" 文件。所以在处理 "import c" 时,它会再次尝试 "import a" 而不是发现 "a" 是它可以跳过的东西。
当Python开始加载pkg.a
模块时,它设置了sys.modules['pkg.a']
到相应的模块对象,但是它只设置了[=13]的a
属性=] 模块对象在加载 pkg.a
模块的最后。这将在以后相关。
相对导入是 from
导入,它们的行为相同。在 from . import whatever
计算出 .
引用了 pkg
包之后,它继续执行常规的 from pkg import whatever
逻辑。
当c.py
命中from . import a
时,首先看到pkg.a
已经在sys.modules
中,说明pkg.a
已经加载或者是在加载过程中。 (它正在加载,但此代码路径并不关心。)它跳到其工作的第二部分,检索 pkg.a
并将其分配给本地名称空间中的 a
名称,但它不只是检索 sys.modules['pkg.a']
来执行此操作。
即使 os.open
是一个函数,而不是一个模块,您知道如何做类似 from os import open
的事情吗?这种导入不能通过 sys.modules['os.open']
,因为 os.open
不是模块,也不在 sys.modules
中。相反,所有 from
导入, 包括所有相关导入 ,尝试在它们从中导入名称的模块上进行属性查找。 from . import a
在 pkg
模块对象上查找 a
属性,但它不存在,因为该属性仅在 pkg.a
完成加载时设置。
在Python2,就这样。导入结束。 ImportError
这里。在 Python 3(特别是 3.5+)上,因为他们想鼓励相对导入并且这种行为确实很不方便,所以 from
导入再尝试一步。如果属性查找失败, 现在 他们尝试 sys.modules
。 pkg.a
在 sys.modules
中,所以导入成功。您可以在 issue 17636.
的 CPython 问题跟踪器中查看有关此更改的讨论
注意:这是关于导入 模块 而不是 类,这些模块的函数,所以我不认为它是鬃毛 "ImportError: cannot import name" 结果的副本,至少我还没有找到匹配的结果。
我知道按名称从模块导入 类 或函数可能会导致问题,因为如果存在循环依赖,模块本身可能还没有完全初始化,但这里不是这种情况。
为了重现这个问题,创建三个循环依赖它的模块。
首先创建一个包:
$ mkdir pkg
$ touch pkg/__init__.py
然后创建pkg/a.py,内容为:
from __future__ import print_function
from __future__ import absolute_import
from . import b
def A(x):
print('I am A, x={}.'.format(x))
b.B(x + 1)
def Z(x):
print('I am Z, x={}. I\'m done now!'.format(x))
和pkg/b.py,内容为:
from __future__ import print_function
from __future__ import absolute_import
from . import c
def B(x):
print('I am B, x={}.'.format(x))
c.C(x * 2)
和pkg/c.py,内容为:
from __future__ import print_function
from __future__ import absolute_import
from . import a
def C(x):
print('I am C, x={}.'.format(x))
a.Z(x ** 2)
还有一个 main.py(在顶级目录中)调用它们:
from __future__ import print_function
from __future__ import absolute_import
from pkg import a
if __name__ == '__main__':
a.A(5)
我预计循环依赖不会有问题,因为在导入期间没有对每个模块中项目的引用(即没有从模块 b 或 c 引用 a.A,除了c.C).
体内的调用而且,确实,运行 这个 python3 工作得很好:
$ python3 main.py
I am A, x=5.
I am B, x=6.
I am C, x=12.
I am Z, x=144. I'm done now!
(这是 Debian Stretch 上的 Python 3.5.3,供记录。)
但是对于 python2 (Python 2.7.13),它并没有真正起作用,它抱怨循环依赖...
$ python main.py
Traceback (most recent call last):
File "main.py", line 5, in <module>
from pkg import a
File "/tmp/circular/pkg/a.py", line 5, in <module>
from . import b
File "/tmp/circular/pkg/b.py", line 5, in <module>
from . import c
File "/tmp/circular/pkg/c.py", line 5, in <module>
from . import a
ImportError: cannot import name a
所以我的问题是:
为什么我 运行 陷入循环依赖问题,如果我不是从我的模块导入或引用特定的 类 或函数,只是模块本身?
为什么这只发生在 Python 2 上? (在 Python 3 中参考 PEP、代码、发行说明或有关修复此问题的文章将不胜感激。)
有什么方法可以避免Python 2 中的这个问题,同时又不破坏模块的循环依赖?我相信并非所有循环依赖都会导致此问题(即使在 Python 2 中),所以我想知道哪些情况是安全的,哪些情况不是...
我不确定 Python 3 是如何解决这个问题的,但我的经验告诉 Python 2 确实无法解决问题。解决问题的正确方法是:
- 注意不要在你的代码中引入这个
- 在你需要的地方导入内部函数
我个人更喜欢后者
关于为什么,Python 中的模块系统在成功加载模块之前不会标记它。所以在你的 "import a",Python 不会知道它已经加载 "a" 直到所有依赖加载,"b" 和 "c" 在它经历整个 "a.py" 文件。所以在处理 "import c" 时,它会再次尝试 "import a" 而不是发现 "a" 是它可以跳过的东西。
当Python开始加载pkg.a
模块时,它设置了sys.modules['pkg.a']
到相应的模块对象,但是它只设置了[=13]的a
属性=] 模块对象在加载 pkg.a
模块的最后。这将在以后相关。
相对导入是 from
导入,它们的行为相同。在 from . import whatever
计算出 .
引用了 pkg
包之后,它继续执行常规的 from pkg import whatever
逻辑。
当c.py
命中from . import a
时,首先看到pkg.a
已经在sys.modules
中,说明pkg.a
已经加载或者是在加载过程中。 (它正在加载,但此代码路径并不关心。)它跳到其工作的第二部分,检索 pkg.a
并将其分配给本地名称空间中的 a
名称,但它不只是检索 sys.modules['pkg.a']
来执行此操作。
即使 os.open
是一个函数,而不是一个模块,您知道如何做类似 from os import open
的事情吗?这种导入不能通过 sys.modules['os.open']
,因为 os.open
不是模块,也不在 sys.modules
中。相反,所有 from
导入, 包括所有相关导入 ,尝试在它们从中导入名称的模块上进行属性查找。 from . import a
在 pkg
模块对象上查找 a
属性,但它不存在,因为该属性仅在 pkg.a
完成加载时设置。
在Python2,就这样。导入结束。 ImportError
这里。在 Python 3(特别是 3.5+)上,因为他们想鼓励相对导入并且这种行为确实很不方便,所以 from
导入再尝试一步。如果属性查找失败, 现在 他们尝试 sys.modules
。 pkg.a
在 sys.modules
中,所以导入成功。您可以在 issue 17636.