Python循环导入?

2024-05-17 02:35:26 发布

您现在位置: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语句,它能工作吗?循环导入有什么不成文的规定吗?如何在调用堆栈的后面使用同一个类?


Tags: infrompydevimportlineutilspost
3条回答

当您第一次导入一个模块(或它的一个成员)时,模块内的代码将像任何其他代码一样按顺序执行;例如,对函数体的处理没有任何不同。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时出错。你不应该使用循环导入。充其量,它有微不足道的好处(通常,nobenefit),并且它会导致这样的问题。它给任何维护它的开发人员带来负担,迫使他们走在蛋壳上以避免破坏它。重构模块组织。

我认为jpmc26的答案,虽然绝不是错误的,但在循环进口上却过于沉重。如果设置正确,它们可以正常工作。

最简单的方法是使用import my_module语法,而不是from my_module import some_object。前者几乎总是有效的,即使my_moduleincluded将我们重新导入。后者仅在my_module中已定义my_object时才起作用,而在循环导入中可能不是这样。

具体到您的案例:尝试将entities/post.py更改为import physics,然后直接引用physics.PostBody,而不仅仅是PostBody。类似地,将physics.py更改为import entities.post,然后使用entities.post.Post,而不仅仅是Post

要理解循环依赖关系,需要记住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。很快它就会在X.py中命中需要Y2的import语句。但是Y2是未定义的,所以编译失败。

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

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

from X import Y

当您认为可能存在循环导入时,还应避免编译时引用其他模块中的变量。考虑一下看起来无辜的代码:

import X
z = X.Y

假设模块X在该模块导入X之前导入该模块。进一步假设Y是在import语句之后的X中定义的。然后,在导入此模块时将不定义Y,您将得到编译错误。如果这个模块先导入Y,你就可以不用管它了。但是当你的一个同事无辜地改变了第三个模块中定义的顺序时,代码就会崩溃。

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

请注意,在模块中向下移动import语句会模糊您正在执行的操作。请在模块顶部添加如下注释来对此进行补偿:

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

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

相关问题 更多 >