问题 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  - 但那是语法错误。

也, from . import module2 在 module1 工作正常,但是 from . import module1 在 module2 不起作用......

更新:

这个黑客有效(但我正在寻找'官方'方式):

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']

2900
2017-11-06 20:46


起源

循环导入? - Facundo Casco
是的 - 循环导入。 module1中的函数声明其参数之一是module2中定义的类的实例,反之亦然。 - warvariuc
必须始终相对进口 from .[optional] import name。 - jfs
@ J.F.Sebastian在Python2中导入使用 import X 实际上也可以是相对的,除非你导入 absolute_imports 从 __future__。 - poke
“我想为我的其他应用程序提供可移植的package1,这就是我想使用相对路径的原因。” - 更好的解决方案是将package1放在它自己的独立中。当然然后它无法导入package2,但如果它可以重用,那么为什么呢? - Lennart Regebro


答案:


更好的解决方案是将package1放在自己独立的包中。当然然后它无法导入package2,但如果它可以重用,那么为什么呢?


6
2017-11-08 23:56





一般应避免循环进口,另见 这是对相关问题的回答, 要么 这篇关于effbot.org的文章

在这种情况下,问题是您导入 from . 哪里 . 是当前的包。所以你的全部 from . import X 进口是通过包的 __init__.py

如果您明确地导入模块,则可以使问题更加明显 __init__.py 并给他们另一个名字(并调整其他进口,当然使用这些名称):

print('Init package1')
from . import module1 as m1
from . import module2 as m2

现在当你导入 m1 在 start.py,包首先初始化 m1 来到了 from . import m2 线。那时,没有 m2 已知的 __init__.py 所以你得到一个导入错误。如果你切换导入语句 __init__.py 周围(所以你加载 m2 第一),然后在 m2 它找到了 from . import m1 线,由于与以前相同的原因而失败。

如果您没有明确地导入模块 __init__.py 类似的东西仍然在后台发生。不同之处在于您获得的结构不太平坦(因为导入不再仅从包中启动)。就这样 module1 和 module2 得到“开始”,你得到相应的初始化打印。

为了使它工作,你可以绝对导入 module2。这样你就可以避免包首先需要解决所有问题,并使其重用导入 start.py (因为它具有相同的导入路径)。

或者甚至更好,你完全摆脱了循环导入。如果您有循环引用,通常表明您的应用程序结构不太好。

(我希望我的解释没有任何意义,我已经很难写了,但总的想法应该很明确,我希望...)

编辑

响应您的更新;你在做什么是你使用完整的包名来获得对模块的引用。这与使其工作的第一个可能选项等效(但要复杂得多);您使用与中的相同导入路径使用绝对导入 start.py


6
2017-11-06 22:25



我需要循环进口。一个功能 module1 断言其参数之一是在中定义的类的实例 module2 反之亦然。 - warvariuc
@warvariuc但是其中一个包含在函数定义中,而不是全局字典。 - Paul Manta
是的,我可以在函数内部的'运行时'进行导入,但不喜欢这种方式。 - warvariuc
有趣的是,那 from . import module2 在module1中运行正常,但是 from . import module1 在module2中失败。 - warvariuc
这是因为首先加载module1,然后引用module2。如果你改变了 start.py 首先导入module2,你将得到错误切换。不,你可能不需要循环导入。重构您的包设计,因此这些类在单个模块中,或者将它们提取到某个基本模块。 - poke


您的更新模拟绝对导入的作用: import package1.module1 如果你这样做的话 module1 被进口。如果您想使用动态父包名称,则导入 module1 在里面 module2.py

import importlib
module1 = importlib.import_module('.module1', __package__)

我需要循环进口。 module1中的一个函数断言其中一个函数   parameter是在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.pymodule{1,2}.py 从中导入 common 在这种情况下。


2
2017-11-07 07:50



package1中的模块非常复杂,重构对我来说很难,这就是为什么我正在寻找一种解决方法。另见。 - warvariuc
@warvariuc:我用答案更新了答案 importlib 例。 - jfs


module2.py
          import module1

也工作。


1
2017-11-06 21:09



不是从主外部调用主脚本时 package1 包。那么你需要一个绝对的导入路径。 - poke
@poke - 为我工作,正如OP所说的那样。 Python2.7,如果重要的话 - Malvolio
是的,这很重要。在Python3中,您只能使用 from..import.. 语法,所以这不起作用。 - poke


接受的答案 Python中的循环导入依赖项 提出一个好点:

如果a取决于c和c取决于a,那么它们实际上不是同一个单位吗?

你应该真正检查为什么你将a和c分成两个包,因为要么你有一些代码你应该拆分成另一个包(使它们都依赖于那个新的包,而不是彼此),或者你应该合并它们成一个包。
   - Lasse V. Karlsen

也许您应该考虑将它们放在同一个模块中。 :)


0
2017-11-07 09:05



我发现这样的(code.google.com/p/web2py/source/browse/gluon/dal.py) 不大好 - warvariuc
@warvariuc是的,这非常令人生畏。但正如引用的答案所示,你可以将一些内容放在另一个模块中(如果这样做有意义),并将相互依赖的内容放在一个文件中。 - Paul Manta
我不喜欢我必须摆弄重构代码,因为语言不允许我使它更好(请参阅问题中的黑客更新,它做我想要的) - warvariuc
对于您选择使用的任何语言,总有些事情无法完成。至少,我没有看到如何避免限制用户 某种程度上来说 在语言设计期间。 :-) - Andres Riofrio


我今天遇到了同样的问题,看来这确实在python3.4中被破坏了,但是在python3.5中运行。

更新日志 有一个条目:

现在支持涉及相对进口的循环进口。 (由Brett Cannon和Antoine Pitrou提供 BPO-17636)。

通过bug报告,似乎这不是一个固定的buf,以及导入工作方式的新功能。参考 戳上面的答案,他表明了这一点 from . import foo 意味着加载 __init__.py 得到 foo 从它(可能来自隐式加载的子模块列表)。从python3.5开始, from . import foo 也会这样做,但如果 foo 作为属性不可用,它将回退到查看已加载模块的列表(sys.modules)看它是否已经存在,这解决了这个特殊情况。不过,我并不是100%确定我是否正确地介绍了它的工作原理。


0
2017-08-29 17:15





确保你的 package1 是一个文件夹。在中创建一个类 __init__.py  - 说 class1。将您的逻辑包含在一个方法下 class1  - 说 method1

现在,编写以下代码 -

from .package1 import class1
class1.method1()

这是我解决它的方式。总而言之,您的根目录是 . 写你的 import 声明使用 . 符号,例如 from .package 要么 from app.package


0
2017-10-25 14:24