Python循环导入?

所以我得到这个错误

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声明,它的工作原理? 有关于循环导入的一些不成文的规定吗? 我如何在调用堆栈下面继续使用同一个类?

当你第一次导入一个模块(或者它的一个成员)时,模块里面的代码就像任何其他代码一样按顺序执行。 例如,它没有任何不同的处理function的身体。 import只是一个像任何其他(赋值,函数调用, defclass )的命令。 假设您的导入发生在脚本的顶部,那么发生了什么事情:

  • 当您尝试从world导入World时, world脚本被执行。
  • world脚本导入Field ,导致entities.field脚本被执行。
  • 这个过程继续下去,直到你到达entities.post脚本,因为你试图导入Post
  • entities.post脚本导致physics模块被执行,因为它试图导入PostBody
  • 最后, physics试图从entities.post导入Post
  • 我不确定entities.post模块是否存在于内存中,但是这并不重要。 模块不在内存中,或者模块还没有Post成员,因为它尚未执行定义Post
  • 无论哪种方式,都会发生错误,因为Post不在那里被导入

所以不,它不是“在调用堆栈中继续工作”。 这是发生错误的堆栈跟踪,这意味着它尝试在该类中导入Post错误。 你不应该使用循环import。 充其量,效益微不足道(通常没有任何好处),并导致这样的问题。 它负担任何开发商维护它,迫使他们走在蛋壳上,以避免打破它。 重构你的模块组织。

我认为jpmc26目前接受的答案在循环import方面过重。 他们可以工作得很好,如果你设置正确。

最简单的方法是使用import my_module语法,而不是from my_module import some_object 。 前者几乎总是可以工作的,即使包含my_module也会导入我们。 后者只有在my_object已经在my_module定义的情况下才起作用,在循环导入中my_object可能不是这种情况。

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

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

进行导入时,会发生什么情况取决于您要导入的文件是否已存在于模块表中。 如果是这样,Python使用符号表中的任何东西。 如果没有,Python开始读取模块文件,编译/执行/导入任何它在那里find。 在编译时引用的符号是否被find,取决于它们是否已经被看到,还是被编译器看到。

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

文件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中触发了导入语句。 由于X.py已经在模块表中,因此Python使用现有的不完整的X.py符号表来满足任何请求的引用。 X.py中的导入语句之前出现的任何符号现在都在符号表中,但之后的任何符号都不是。 由于X1现在出现在导入语句之前,因此已成功导入。 Python然后继续编译Y.py. 这样做就定义了Y2并完成了编译Y.py。 然后继续编译X.py,并在Y.py符号表中findY2。 编译最终完成没有错误。

如果您尝试从命令行编译Y.py,会发生非常不同的事情。 编译Y.py时,编译器在定义Y2之前先触发导入语句。 然后开始编译X.py. 不久它就会触发需要Y2的X.py中的import语句。 但Y2是未定义的,所以编译失败。

请注意,如果修改X.py来导入Y1,无论编译哪个文件,编译都会成功。 但是,如果修改文件Y.py来导入符号X2,则不会编译任何文件。

任何时候当模块X或任何由X导入的模块都可能导入当前模块时,不要使用:

 from X import Y 

任何时候你认为可能有循环导入,你也应该避免编译时引用其他模块中的variables。 考虑无辜的代码:

 import X z = XY 

假设模块X在该模块导入X之前导入该模块。进一步假设在导入语句之后的X中定义了Y. 那么当这个模块被导入的时候Y就不会被定义,而且会得到一个编译错误。 如果这个模块先inputY,那么你可以避开它。 但是当你的一个同事在第三个模块中天真地改变了定义的顺序时,代码将会被破坏。

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

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

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

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

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

“…要引用另一个应用程序中定义的模型,可以使用完整的应用程序标签明确指定一个模型,例如,如果上面的Manufacturer模型是在另一个名为production的应用程序中定义的,则需要使用:

 class Car(models.Model): manufacturer = models.ForeignKey( 'production.Manufacturer', on_delete=models.CASCADE, ) 

解决两个应用程序之间的循环导入依赖关系时,这种引用可能很有用。 ……”