Python循环导入?

Posted

技术标签:

【中文标题】Python循环导入?【英文标题】:Python circular importing? 【发布时间】:2014-04-06 21:25:31 【问题描述】:

所以我收到了这个错误

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

你可以看到我在前面使用了相同的导入语句并且它有效吗?循环进口有什么不成文的规定吗?如何在调用堆栈的下方使用相同的类?

【问题讨论】:

【参考方案1】:

我认为answer by 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

【讨论】:

这个答案与相对导入兼容吗? 为什么会这样? 说非from 语法总是有效是错误的。如果我在模块 a 中有 class A(object): pass; class C(b.B): pass,在模块 b 中有 class B(a.A): pass,那么循环导入仍然是一个问题,这将不起作用。 你是对的,模块顶层代码中的任何循环依赖(例如您的示例中的类声明的基类)都会成为问题。在这种情况下,jpmc 关于您应该重构模块组织的回答可能是 100% 正确的。要么将B 类移动到a 模块中,要么将C 类移动到b 模块中,这样你就可以打破循环。还值得注意的是,即使只有一个圆的方向涉及***代码(例如,如果类 C 不存在),您可能会收到错误,具体取决于哪个模块由其他代码先导入。 @TylerCrompton:我不确定您所说的“模块导入必须是绝对的”是什么意思。循环相对导入可以工作,只要你导入模块,而不是它们的内容(例如from . import sibling_module,而不是from .sibling_module import SomeClass)。当包的__init__.py 文件参与循环导入时,会有一些更微妙的地方,但这个问题既罕见,也可能是import 实现中的一个错误。请参阅Python bug 23447,我为它提交了一个补丁(唉,一直在苦苦挣扎)。【参考方案2】:

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

【讨论】:

应该是isinstance(userData, Post)。无论如何,你别无选择。循环导入不起作用。你有循环进口的事实对我来说是一种代码味道。它建议您有一些功能应该移出到第三个模块。如果不查看整个课程,我无法说出什么。 @CpILL 过了一会儿,我确实想到了一个非常老套的选项。如果您现在无法解决此问题(由于时间限制或您有什么),那么您可以在您使用它的方法中本地执行您的导入。 def 中的函数体在调用该函数之前不会执行,因此在您实际调用该函数之前不会发生导入。到那时,imports 应该可以工作,因为其中一个模块将在调用之前完全导入。这是一个绝对令人作呕的黑客,它不应该在你的代码库中保留很长时间。 我认为您的答案在循环导入方面过于苛刻。如果你只做import foo 而不是from foo import Bar,循环导入通常会起作用。这是因为大多数模块只是定义稍后运行的东西(如函数和类)。导入时执行重要操作的模块(例如不受 if __name__ == "__main__" 保护的脚本)可能仍然存在问题,但这并不常见。 @Blckknght 我认为,如果您使用循环导入,您将花费时间在其他人将不得不调查和困惑的奇怪问题上。它们迫使你花时间小心不要被它们绊倒,最重要的是你的设计需要重构的代码味道。关于它们在技术上是否可行,我可能错了,但它们是一个糟糕的设计选择,迟早会导致问题。清晰和简单是编程中的圣杯,而循环导入在我的书中违反了这两者。 或者;您将功能拆分得太多了,这就是循环导入的原因。如果你有两件事情一直相互依赖;最好将它们放在一个文件中。 Python 不是 Java;没有理由不将功能/类分组到一个文件中以防止奇怪的导入逻辑。 :-)【参考方案3】:

要理解循环依赖,您需要记住 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。编译最终完成,没有错误。

如果您尝试从命令行编译 Y.py,会发生一些非常不同的事情。在编译 Y.py 时,编译器会在定义 Y2 之前命中 import 语句。然后它开始编译 X.py。很快它就遇到了需要 Y2 的 X.py 中的导入语句。但是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)

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

【讨论】:

我认为python中根本没有编译器或编译时间 Python does 有一个编译器,并且 is 已编译 @pkqxdd ,编译通常只是隐藏在用户之外。这可能有点令人困惑,但如果不参考 Python 的、有些模糊的“编译时间”,作者将很难给出这个令人钦佩的清晰描述。 @pkqxdd nedbatchelder.com/blog/201803/… 我继续在我的机器上尝试这个并得到了不同的结果。 Ran X.py 但出现错误“无法从'Y'导入名称'Y2'”。不过,运行 Y.py 没有问题。我在 Python 3.7.5 你能帮忙解释一下这里的问题吗? 这个答案符合我的直觉,但我在 Python 3.9.6 上看到与@xuefenghuang 相同的错误。当前的行为是否可能更严格一些,即您可以循环导入部分初始化的模块,但不能从部分初始化的模块中导入特定名称?【参考方案4】:

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

"...要引用另一个应用程序中定义的模型,您可以显式指定具有完整应用程序标签的模型。例如,如果上面的制造商模型是在另一个名为生产的应用程序中定义的,则需要使用:

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

这种引用在解决两个应用程序之间的循环导入依赖关系时很有用。..."

【讨论】:

我知道我不应该使用评论说“谢谢”,但这已经困扰了我几个小时。谢谢谢谢谢谢!!! 我同意@MikeyE。我已经阅读了几个博客和 ***s 试图用 PonyORM 来解决这个问题。在其他人说这是不好的做法的地方,或者你为什么要将你的类编码为循环的,那么 ORM 正是发生这种情况的地方。因为很多示例将所有模型放在同一个文件中,并且我们遵循这些示例,除了我们为每个文件使用一个模型之外,当 Python 无法编译时问题并不清楚。然而,答案是如此简单。正如迈克所指出的,非常感谢。【参考方案5】:

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

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

【讨论】:

python多么优雅【参考方案6】:

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

【讨论】:

【参考方案7】:

我正在使用以下内容:

from module import Foo

foo_instance = Foo()

但为了摆脱circular reference,我做了以下事情并且成功了:

import module.foo

foo_instance = foo.Foo()

【讨论】:

以上是关于Python循环导入?的主要内容,如果未能解决你的问题,请参考以下文章

Python 1-2模块的循环导入问题

Python循环导入?

Python中的循环导入依赖

Python中的循环模块依赖和相对导入

python 循环导入再次(也就是这个设计有啥问题)

循环导入的python解决方法