Python 3 中的相对导入问题

13 投票
7 回答
13907 浏览
提问于 2025-04-17 05:45

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 个回答

2

你的更新模拟了绝对导入的效果: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中进行导入。

6

一般来说,应该避免循环导入,更多信息可以参考这个相关问题的回答,或者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中明确导入模块,后台还是会发生类似的事情。不同的是,你会得到一个不那么平坦的结构(因为导入不再仅仅从包开始)。这样,module1module2都会被“启动”,并且你会看到相应的初始化打印信息。

为了让它正常工作,你可以在module2中使用绝对导入。这样可以避免包需要先解析所有内容,并让它重用start.py中的导入(因为它们有相同的导入路径)。

更好的办法是完全摆脱循环导入。如果你的应用结构中有循环引用,通常意味着结构设计得不太好。

(我希望我的解释有意义,我写的时候已经有点困难了,但我希望大致的想法是清楚的…)

编辑

针对你的更新;你在这里做的是使用完整的包名来获取模块的引用。这相当于(但复杂得多)让它工作的第一种可能选项;你使用绝对导入,路径和start.py中的相同。

6

解决你问题的一个更好的办法是把package1放到一个单独的包里。当然,这样的话它就不能导入package2了,但如果package1是可以重复使用的,那它为什么还需要导入package2呢?

撰写回答