Python循环导入?

时间:2014-03-05 02:26:05

标签: 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

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

7 个答案:

答案 0 :(得分:121)

我认为jpmc26的答案,虽然决不是错误的,但对于循环导入来说太过分了。如果你正确设置它们,它们可以正常工作。

最简单的方法是使用import my_module语法,而不是from my_module import some_object。前者几乎总是有效,即使包含my_module也会导致我们回归。后者仅在my_object中已定义my_module时才有效,在圆形导入中可能不是这种情况。

具体针对您的情况:尝试更改entities/post.py以执行import physics,然后直接引用physics.PostBody而不是PostBody。同样,将physics.py更改为import entities.post,然后使用entities.post.Post而不只是Post

答案 1 :(得分:45)

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

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

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

答案 2 :(得分:28)

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

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

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

档案X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

档案Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

现在假设你编译文件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

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

import X
z = X.Y

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

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

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

#import X   (actual import moved down to avoid circular dependency)

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

答案 3 :(得分:9)

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

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

function updateInventory ($inv) {
    foreach($_SESSION["inventory"] as &$bookDetails) {
        foreach($_POST['orders'] as $k => $v) {
            if($bookDetails['title'] == $k && $v == "hardcover") {
                $bookDetails['hc-quantity']--;     
            }      
        }
    }
}

在解决两个应用程序之间的循环导入依赖关系时,此类引用非常有用。 ...&#34;

答案 4 :(得分:2)

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

enter image description here

答案 5 :(得分:0)

我正在使用以下内容:

from module import Foo

foo_instance = Foo()

但要摆脱circular reference,我做了以下工作,它起作用了:

import module.foo

foo_instance = foo.Foo()

答案 6 :(得分:0)

我能够(仅)将需要该模块中对象的功能导入模块:

def my_func():
    import Foo
    foo_instance = Foo()