注意:这是关于导入模块,而不是从这些模块中导入类或函数,因此,我认为这与“ ImportError:无法导入”名称”的结果,因此,至少我没有找到与此匹配的内容。
我确实知道按名称从模块中导入类或函数可能会引起问题,因为如果存在循环依赖关系,则模块本身可能尚未完全初始化,但这不是这种情况。
为了重现此问题,请创建三个对其具有循环依赖性的模块。
首先创建一个包:
$ 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引用aA) cC)。
而且,实际上,使用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上? (参考PEP,代码,发行说明或有关Python 3中此问题的修补程序的文章。)
有什么方法可以避免Python 2中的此问题,同时又不会破坏模块的循环依赖关系?我相信并非所有循环依赖项都会导致此问题(即使在Python 2中也是如此),所以我想知道哪些情况是安全的,哪些情况不是...
答案 0 :(得分:4)
Python开始加载pkg.a
模块时,它会将sys.modules['pkg.a']
设置为相应的模块对象,但仅将a
模块对象的pkg
属性设置为加载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']
来完成此操作。
即使from os import open
是函数而不是模块,您知道如何处理os.open
之类的东西吗?这种导入无法通过sys.modules['os.open']
进行,因为os.open
不是模块,也不在sys.modules
中。相反,所有from
导入(包括所有相对导入)都尝试在要从中导入名称的模块上进行属性查找。 from . import a
在a
模块对象上查找pkg
属性,但它不存在,因为该属性仅在pkg.a
完成加载时设置。
在Python 2上就是这样。导入结束。 ImportError
在这里。在Python 3(尤其是3.5+)上,由于他们想鼓励相对导入,而这种行为确实很不方便,因此from
导入又尝试了一步。如果属性查找失败,请 now 尝试sys.modules
。 pkg.a
在sys.modules
中,因此导入成功。您可以在issue 17636的CPython问题跟踪器中查看有关此更改的讨论。
答案 1 :(得分:0)
我不确定Python 3如何解决该问题,但我的经验告诉我Python 2确实无法使其正常工作。解决此问题的正确方法是:
我个人更喜欢后者。
为什么,Python中的模块系统将不会标记成功加载的模块。因此,在您“导入a”时,Python将不会知道它已经加载了“ a”,直到完成整个“ a.py”文件中的所有相关加载“ b”和“ c”为止。因此,在处理“导入c”时,它将再次尝试“导入a”,而不是发现“ a”是可以跳过的内容。