为什么循环导入似乎在调用堆栈中进一步工作,但随后又在调用堆栈中进一步引发 Import Error?

我得到了这个错误

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 语句,并且运行良好。关于循环导入有什么不成文的规定吗?如何在调用堆栈的下面使用相同的类?


另请参阅 在 Python 中使用相互导入或循环导入时会发生什么?,了解什么是允许的以及什么导致了 WRT 循环导入的问题。有关解决和避免循环依赖关系的技术,请参见 对于“ Import Error: Can not import name X”或“ AttributeError: ... (很可能是由于循环导入)”,我能做些什么?

171087 次浏览

当您第一次导入一个模块(或它的一个成员)时,模块内的代码会像其他代码一样按顺序执行; 例如,它的处理方式与函数体没有什么不同。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时出错。您不应该使用循环导入。最好的情况是,它的好处可以忽略不计(通常是 没有的好处) ,而且它会导致这样的问题。任何维护它的开发人员都会感到负担,迫使他们在蛋壳上行走以避免破坏它。重构模块组织。

我认为,尽管 作者: jpmc26决不是 错了,但它在循环进口方面下降得太厉害了。如果设置正确,它们可以正常工作。

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

针对您的情况: 尝试将 entities/post.py改为 import physics,然后直接引用 physics.PostBody而不仅仅是 PostBody。类似地,将 physics.py改为 import entities.post,然后使用 entities.post.Post而不仅仅是 Post

对于那些像我一样来自姜戈的人,你们应该知道这些文档提供了一个解决方案: Https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

”... 要引用在另一个应用程序中定义的模型,您可以显式地指定带有完整应用程序标签的模型。例如,如果上面的 Manufacturers 模型是在另一个称为 production 的应用程序中定义的,那么您需要使用:

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

这种类型的引用在解决两个应用程序之间的循环导入依赖关系时非常有用。 ...”

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

执行导入时,所发生的情况取决于所导入的文件是否已经存在于模块表中。如果是这样,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。编译最终完成 w/o 错误。

如果尝试从命令行编译 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 在导入语句之后的 X 中定义。那么在导入这个模块时将不会定义 Y,并且您将得到一个编译错误。如果这个模块首先导入 Y,那么您就可以不受影响。但是,当您的一个同事无意中更改了第三个模块中定义的顺序时,代码就会中断。

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

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

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

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

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

enter image description here

我使用了以下方法:

from module import Foo


foo_instance = Foo()

但为了摆脱 circular reference,我采取了以下措施,而且奏效了:

import module.foo


foo_instance = foo.Foo()

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

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

根据这个 回答,我们可以在代码块中导入另一个模块的对象(如函数/方法等) ,而不会出现循环导入错误,例如导入简单的 another.py模块对象,你可以使用:

def get_simple_obj():
from another import Simple
return Simple


class Example(get_simple_obj()):
pass


class NotCircularImportError:
pass

在这种情况下,another.py模块可以很容易地导入 NotCircularImport Error,没有任何问题。

只要检查您的文件名,看看它是否与您正在导入的库相同。

同情,同情

import sympy as sym