Python 3 中的相对导入问题
Python 的导入让我很头疼(我在使用 Python 导入时,有时候完全不符合“显式优于隐式”的说法 :( ):
[app]
start.py
from package1 import module1
[package1]
__init__.py
print('Init package1')
module1.py
print('Init package1.module1')
from . import module2
module2.py
print('Init package1.module2')
import sys, pprint
pprint.pprint(sys.modules)
from . import module1
我遇到的问题是:
vic@ubuntu:~/Desktop/app2$ python3 start.py
Init package1
Init package1.module1
Init package1.module2
{'__main__': <module '__main__' from 'start.py'>,
...
'package1': <module 'package1' from '/home/vic/Desktop/app2/package1/__init__.py'>,
'package1.module1': <module 'package1.module1' from '/home/vic/Desktop/app2/package1/module1.py'>,
'package1.module2': <module 'package1.module2' from '/home/vic/Desktop/app2/package1/module2.py'>,
...
Traceback (most recent call last):
File "start.py", line 3, in <module>
from package1 import module1
File "/home/vic/Desktop/app2/package1/module1.py", line 3, in <module>
from . import module2
File "/home/vic/Desktop/app2/package1/module2.py", line 5, in <module>
from . import module1
ImportError: cannot import name module1
vic@ubuntu:~/Desktop/app2$
import package1.module1
可以正常工作,但我想用 from . import module1
,因为我想让 package1
可以在我的其他应用中使用,所以我想用相对路径。
我使用的是 Python 3。
我需要循环导入。module1
中的一个函数需要确认它的一个参数是 module2
中定义的一个类的实例,反之亦然。
换句话说:
sys.modules
包含 'package1.module1': <module 'package1.module1' from '/home/vic/Desktop/app2/package1/module1.py'>
。我想以 from . import module1
的形式获取对它的引用,但它试图获取一个名称,而不是像 import package1.module1
那样获取一个包(后者可以正常工作)。我尝试了 import .module1 as m1
- 但这会导致语法错误。
此外,module1
中的 from . import module2
可以正常工作,但在 module2
中的 from . import module1
就不行了...
更新:
这个方法可以用(但我在寻找“官方”的方式):
print('Init package1.module2')
import sys, pprint
pprint.pprint(sys.modules)
#from . import module1
parent_module_name = __name__.rpartition('.')[0]
module1 = sys.modules[parent_module_name + '.module1']
7 个回答
你的更新模拟了绝对导入的效果:import package1.module1
,这意味着在导入module1
的时候可以这样做。如果你想在module2.py
中使用动态的父包名称来导入module1
,可以这样做:
import importlib
module1 = importlib.import_module('.module1', __package__)
我需要循环导入。
module1
中的一个函数要求它的一个参数是module2
中定义的类的实例,反之亦然。
你可以把其中一个类移动到一个单独的模块中,这样就能解决循环依赖的问题;或者如果你不想使用绝对导入,可以在函数内部进行导入。
.
├── start.py
# from package1 import module1
└── package1
├── __init__.py
# print("Init package1")
# from . import module1, module2
├── c1.py
# print("Init package1.c1")
# class C1:
# pass
├── module1.py
# print("Init package1.module1")
# from .c1 import C1
# from .module2 import C2
└── module2.py
# print("Init package1.module2")
# from .c1 import C1
# class C2:
# pass
# def f():
# from .module1 import C1
输出
Init package1
Init package1.module1
Init package1.c1
Init package1.module2
还有一个可能比重构c1.py
更简单的选项,就是把module{1,2}.py
合并成一个common.py
。在这种情况下,module{1,2}.py
会从common
中进行导入。
一般来说,应该避免循环导入,更多信息可以参考这个相关问题的回答,或者effbot.org上的这篇文章。
在这个情况下,问题出在你使用了from .
来导入,其中.
代表当前的包。所以你所有的from . import X
导入都会经过这个包的__init__.py
文件。
如果你在__init__.py
中明确导入你的模块,并给它们起个别名(当然,其他导入也要相应调整),那么问题会更明显:
print('Init package1')
from . import module1 as m1
from . import module2 as m2
现在,当你在start.py
中导入m1
时,包会先初始化m1
,然后到达from . import m2
这一行。此时,__init__.py
中并没有已知的m2
,所以你会遇到导入错误。如果你在__init__.py
中调整导入顺序(先加载m2
),那么在m2
中会找到from . import m1
这一行,这个也会因为同样的原因失败。
如果你没有在__init__.py
中明确导入模块,后台还是会发生类似的事情。不同的是,你会得到一个不那么平坦的结构(因为导入不再仅仅从包开始)。这样,module1
和module2
都会被“启动”,并且你会看到相应的初始化打印信息。
为了让它正常工作,你可以在module2
中使用绝对导入。这样可以避免包需要先解析所有内容,并让它重用start.py
中的导入(因为它们有相同的导入路径)。
更好的办法是完全摆脱循环导入。如果你的应用结构中有循环引用,通常意味着结构设计得不太好。
(我希望我的解释有意义,我写的时候已经有点困难了,但我希望大致的想法是清楚的…)
编辑
针对你的更新;你在这里做的是使用完整的包名来获取模块的引用。这相当于(但复杂得多)让它工作的第一种可能选项;你使用绝对导入,路径和start.py
中的相同。
解决你问题的一个更好的办法是把package1放到一个单独的包里。当然,这样的话它就不能导入package2了,但如果package1是可以重复使用的,那它为什么还需要导入package2呢?