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
和其他命令一样只是一个命令(赋值、函数调用、def
、class
)。假设您的导入发生在脚本的顶部,那么这就是正在发生的事情:
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
中的函数体在调用该函数之前不会执行,因此在您实际调用该函数之前不会发生导入。到那时,import
s 应该可以工作,因为其中一个模块将在调用之前完全导入。这是一个绝对令人作呕的黑客,它不应该在你的代码库中保留很长时间。
我认为您的答案在循环导入方面过于苛刻。如果你只做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循环导入?的主要内容,如果未能解决你的问题,请参考以下文章