Python循环导入?

CpI*_*ILL 89 python import circular-dependency

所以我收到了这个错误

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post
Run Code Online (Sandbox Code Playgroud)

你可以看到我进一步使用相同的import语句并且它有效吗?关于循环导入是否有一些不成文的规则?如何在调用堆栈中进一步使用相同的类?

Blc*_*ght 134

我认为jpmc26的答案,虽然没有,但在循环进口方面过于沉重.如果你正确设置它们,它们可以正常工作.

最简单的方法是使用import my_module语法,而不是from my_module import some_object.前者几乎总是有效,即使my_module包括进口我们.后者仅在my_object已经定义my_module的情况下才有效,在循环导入中可能不是这种情况.

具体到你的情况:尝试改变entities/post.pyimport physics,然后参考physics.PostBody而不是PostBody直接.同样,改变physics.pyimport entities.post,然后使用entities.post.Post而不仅仅是Post.

  • 为什么会这样? (14认同)
  • 这个答案是否与相关进口兼容? (4认同)
  • 说非`from`语法总能奏效是错误的.如果我有'A类(对象):通过; class C(bB):模块a中的pass`和模块b中的`class B(aA):pass`然后循环导入仍然是一个问题,这是行不通的. (3认同)
  • 您说得对,模块顶级代码中的任何循环依赖(例如示例中类声明的基类)都将是一个问题。在这种情况下,jpmc 关于您应该重构模块组织的回答可能是 100% 正确的。要么将“B”类移动到模块“a”中,要么将“C”类移动到模块“b”中,这样​​您就可以打破循环。还值得注意的是,即使只有圆的一个方向涉及顶级代码(例如,如果类 `C` 不存在),你*可能*会得到一个错误,这取决于哪个模块首先被其他代码导入. (2认同)
  • @TylerCrompton:我不确定“模块导入必须是绝对的”是什么意思。只要您要导入模块,循环相对导入就可以工作,而不是它们的内容(例如,from .import sibling_module`,而不是from from .sibling_module import SomeClass)。循环导入涉及包的__init__.py文件时,还有一些细微之处,但这问题很少见,并且可能是import实现中的错误。请参阅[Python错误23447](http://bugs.python.org/issue23447),我为此提交了一个补丁(可惜的是,该补丁一直没有成功)。 (2认同)
  • 循环导入通常(但并非总是)是设计不佳或需要重构的证据。 (2认同)

jpm*_*c26 48

当您第一次导入模块(或其成员)时,模块内的代码将像任何其他代码一样顺序执行; 例如,功能的主体没有任何不同的对待.An import就像任何其他命令(赋值,函数调用def,class).假设您的导入发生在脚本的顶部,那么这就是发生的事情:

  • 当您尝试从中导入Worldworld,world脚本将被执行.
  • world脚本的进口Field,这将导致entities.field脚本得到执行.
  • 此过程将继续,直到entities.post您尝试导入脚本为止Post
  • entities.post脚本导致physics模块被执行,因为它尝试导入PostBody
  • 最后,physics尝试导入Postentities.post
  • 我不确定该entities.post模块是否存在于内存中,但它确实无关紧要.模块不在内存中,或者模块还没有Post成员,因为它尚未执行定义Post
  • 无论哪种方式,都会发生错误,因为Post无法导入

所以不,它不是"在调用堆栈中进一步工作".这是发生错误的堆栈跟踪,这意味着它错误地尝试导入Post该类.你不应该使用循环导入.充其量,它具有可忽略的好处(通常没有任何好处),并且会导致这样的问题.它会给任何维护它的开发人员带来负担,迫使他们走在蛋壳上以避免破坏它.重构您的模块组织.

  • 我认为你的答案对循环进口来说太难了.如果你只是`import foo`而不是`来自foo import Bar`,那么循环导入通常会起作用.这是因为大多数模块只定义稍后运行的东西(如函数和类).在导入它们时执行重要操作的模块(如不受`if __name__ =="__ main __"`保护的脚本)可能仍然有问题,但这并不常见. (15认同)
  • @Blckknght我认为你正在花时间处理其他人必须调查的奇怪问题,如果你使用循环导入则会感到困惑.它们迫使你花时间小心不要绊倒它们,最重要的是你的设计需要重构的代码味道.我可能错了它们是否在技术上可行,但它们是一个可怕的设计选择,注定迟早会引起问题.清晰和简洁是编程中的圣杯,循环导入在我的书中违反了两者. (6认同)
  • 另外; 你太过分裂你的功能,这是循环导入的原因.如果你有两件事情一直相互依赖*; 将它们放在一个文件中可能是最好的.Python不是Java; 没有理由不将功能/类分组到单个文件中以防止奇怪的导入逻辑.:-) (5认同)
  • @CpILL过了一会儿,我确实遇到了一个非常糟糕的选择.如果你现在无法解决这个问题(由于时间限制或你有什么),那么你*可以*在你使用它的方法中进行本地导入.在调用函数之前,不会执行`def`中的函数体,因此在您实际调用函数之前不会执行导入.到那时,`import`s应该有效,因为其中一个模块在调用之前已完全导入.这是一个绝对恶心的黑客攻击,它不应该留在你的代码库中任何相当长的时间. (3认同)

Gen*_*son 33

要理解循环依赖关系,您需要记住Python本质上是一种脚本语言.在编译时执行方法之外的语句.Import语句就像方法调用一样执行,为了理解它们,您应该像方法调用一样考虑它们.

导入时,会发生什么取决于您导入的文件是否已存在于模块表中.如果是这样,Python使用符号表中当前的任何内容.如果没有,Python开始读取模块文件,编译/执行/导入它在那里找到的任何内容.编译时引用的符号是否已找到,具体取决于它们是否已被查看,或者尚未被编译器看到.

想象一下,你有两个源文件:

文件X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"
Run Code Online (Sandbox Code Playgroud)

档案Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"
Run Code Online (Sandbox Code Playgroud)

现在假设你编译文件X.py. 编译器首先定义方法X1,然后在X.py中命中import语句.这会导致编译器暂停X.py的编译并开始编译Y.py. 此后不久编译器在Y.py中命中import语句.由于X.py已经在模块表中,因此Python使用现有的不完整X.py符号表来满足所请求的任何引用.在X.py中的import语句之前出现的任何符号现在都在符号表中,但之后的任何符号都不是.由于X1现在出现在import语句之前,因此已成功导入.然后Python继续编译Y.py. 这样做它定义Y2并完成编译Y.py. 然后它继续编译X.py,并在Y.py符号表中找到Y2.编译最终完成没有错误.

如果您尝试从命令行编译Y.py,则会发生非常不同的情况.在编译Y.py时,编译器在定义Y2之前命中import语句.然后它开始编译X.py. 很快就会遇到需要Y2的X.py中的import语句.但Y2未定义,因此编译失败.

请注意,如果您修改X.py以导入Y1,则无论您编译哪个文件,编译都将始终成功.但是,如果您修改文件Y.py以导入符号X2,则两个文件都不会编译.

每当模块X或X导入的任何模块可能导入当前模块时,请勿使用:

from X import Y
Run Code Online (Sandbox Code Playgroud)

只要您认为可能存在循环导入,您还应该避免对其他模块中的变量进行编译时引用.考虑无辜的代码:

import X
z = X.Y
Run Code Online (Sandbox Code Playgroud)

假设模块X在此模块导入X之前导入此模块.进一步假设在导入语句之后在X中定义了Y. 然后在导入此模块时不会定义Y,并且您将收到编译错误.如果此模块首先导入Y,您可以逃脱它.但是当你的一个同事无意中改变了第三个模块中定义的顺序时,代码就会破裂.

在某些情况下,您可以通过将import语句向下移动到其他模块所需的符号定义下来解决循环依赖关系.在上面的示例中,import语句之前的定义永远不会失败.import语句之后的定义有时会失败,具体取决于编译顺序.您甚至可以将import语句放在文件的末尾,只要在编译时不需要导入的符号即可.

请注意,在模块中向下移动import语句会掩盖您正在执行的操作.通过模块顶部的注释对此进行补偿,如下所示:

#import X   (actual import moved down to avoid circular dependency)
Run Code Online (Sandbox Code Playgroud)

一般来说,这是一种不好的做法,但有时很难避免.

  • Python*有*编译器,*是*编译@pkqxdd,编译通常只是远离用户隐藏.这可能有点令人困惑,但如果没有对Python的一些参考,有点模糊,"编译时间",作者很难给出令人钦佩的清晰描述. (5认同)
  • 我认为python中根本没有编译器或编译时间 (2认同)
  • 我继续在我的机器上尝试这个并得到了不同的结果。Ran X.py 但出现错误“无法从‘Y’导入名称‘Y2’”。不过,跑 Y.py 没有问题。我使用的是 Python 3.7.5 你能帮忙解释一下这里的问题是什么吗? (2认同)

Mal*_*umi 14

对于那些像我一样从Django来到这个问题的人,您应该知道文档提供了一个解决方案:https: //docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"...要引用另一个应用程序中定义的模型,您可以使用完整的应用程序标签明确指定模型.例如,如果上面的制造商模型在另一个名为production的应用程序中定义,则需要使用:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)
Run Code Online (Sandbox Code Playgroud)

在解析两个应用程序之间的循环导入依赖关系时,此类引用非常有用......."

  • 我知道我不应该用评论来说"谢谢",但这已经困扰了我几个小时.谢谢你,谢谢你,谢谢你!!! (5认同)

Ale*_*ert 8

我能够在需要来自该模块的对象的函数(仅)中导入模块:

def my_func():
    import Foo
    foo_instance = Foo()
Run Code Online (Sandbox Code Playgroud)

  • python 多么优雅 (3认同)

And*_*röm 6

如果您在相当复杂的应用程序中遇到此问题,那么重构所有导入可能会很麻烦。PyCharm 为此提供了一个快速修复,它也会自动更改导入符号的所有用法。

在此输入图像描述