Python循环导入?


98

所以我得到这个错误

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语句,并且有效吗?关于循环导入是否有一些不成文的规定?如何在调用堆栈的更下方使用相同的类?

Answers:


161

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

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

要针对您的具体情况:尝试更改entities/post.py为do import physics,然后引用physics.PostBody而不是PostBody直接引用。同样,更改physics.py为do import entities.post,然后使用entities.post.Post而不是just Post


5
这个答案与相对进口兼容吗?
2014年

17
为什么会这样?
Juan Pablo Santos

4
说非from语法将一直有效是错误的。如果我class A(object): pass; class C(b.B): pass在模块a和class B(a.A): pass模块b中都有,那么循环导入仍然是一个问题,它将无法正常工作。
CrazyCasta

1
没错,模块顶级代码中的任何循环依赖项(例如示例中的类声明的基类)都将成为问题。在这种情况下,您应该重构jpmc的答案以重构模块组织可能是100%正确的。可以将类移动B到模块中a,也可以将类移动C到模块中,b这样可以打破循环。还值得注意的是,即使圆的仅一个方向涉及顶级代码(例如,如果类C不存在),您也可能会遇到错误,具体取决于哪个模块首先被其他代码导入。
Blckknght

2
@TylerCrompton:我不确定“模块导入必须是绝对的”是什么意思。只要您要导入模块,循环相对导入就可以工作,而不是其内容(例如from . import sibling_module,不是from .sibling_module import SomeClass)。__init__.py循环导入中涉及软件包的文件时,还有一些细微之处,但这个问题既罕见,也可能是import实现中的错误。请参阅Python错误23447,我为此提交了一个补丁(可惜的是,它一直没有成功)。
Blckknght

51

首次导入模块(或其成员)时,模块内的代码将像其他任何代码一样顺序执行。例如,对函数主体的处理没有任何区别。An import只是一个与其他命令一样的命令(赋值,函数调用defclass)。假设您的导入发生在脚本的顶部,那么将发生以下情况:

  • 当您尝试从导入Worldworldworld脚本将被执行。
  • world脚本的进口Field,这将导致entities.field脚本得到执行。
  • 这个过程一直持续到您到达entities.post脚本为止,因为您尝试导入Post
  • entities.post脚本导致physics模块被执行,因为它尝试导入PostBody
  • 最后,physics尝试Postentities.post
  • 我不确定该entities.post模块是否已存在于内存中,但这并不重要。该模块不在内存中,或者该模块还没有Post成员,因为该模块尚未完成执行定义Post
  • 无论哪种方式,都会发生错误,因为Post那里没有要导入的错误

因此,它不是“在调用堆栈中进一步发挥作用”。这是错误发生位置的堆栈跟踪,这意味着它在尝试导入Post该类时出错。您不应该使用循环导入。充其量,它的收益微不足道(通常没有收益),并且会引起类似的问题。这给所有开发人员维护它带来了负担,迫使他们在蛋壳上行走以避免损坏它。重构您的模块组织。


1
应该是isinstance(userData, Post)。无论如何,您别无选择。循环导入无效。您具有循环进口的事实对我来说是一种代码气味。它建议您具有一些应移至第三个模块的功能。如果不看全部两堂课,我不能说什么。
jpmc26 2014年

3
@CpILL过了一会儿,我确实遇到了一个非常棘手的选择。如果您暂时无法执行此操作(由于时间限制或您有什么原因),则可以在使用它的方法中本地进行导入。在调用def该函数之前,不会执行内部的函数主体,因此只有在您实际调用该函数后,导入才会发生。届时,imports应该可以正常工作,因为其中一个模块将在调用之前完全导入。那是绝对令人作呕的黑客,并且不应将其保留在您的代码库中任何长时间。
jpmc26 2014年

15
我认为您对循环进口的回答太难了。如果你只是圆进口平时的工作import foo,而不是from foo import Bar。这是因为大多数模块仅定义稍后运行的内容(例如函数和类)。导入重要模块的模块(例如不受protect保护的脚本if __name__ == "__main__")可能仍然很麻烦,但这并不是太常见。
Blckknght 2014年

6
@Blckknght我认为您正在准备将时间花在解决其他怪异问题上,如果您使用循环进口,其他人将不得不对其进行调查和困惑。它们迫使您花时间小心,不要绊倒它们,最重要的是,您的设计需要重构代码臭味。对于它们在技术上是否可行,我可能是错的,但它们注定是一个糟糕的设计选择,注定迟早会引起问题。清晰和简单是编程中的圣旨,而循环导入在我的书中都违反了。
jpmc26 2014年

6
或者 您对功能的划分过多,这就是循环导入的原因。如果您有互相依赖两件事情所有的时间 ; 最好将它们放在一个文件中。Python不是Java;它不是Java。没有理由不将功能/类分组到一个文件中,以防止奇怪的导入逻辑。:-)
Mark Ribau 2015年

40

要了解循环依赖关系,您需要记住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。很快,它在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 X   (actual import moved down to avoid circular dependency)

通常,这是一个不好的做法,但有时很难避免。


2
我认为python中根本没有编译器或编译时
pkqxdd

6
Python 确实有一个编译器,并且编译@pkqxdd,编译通常只对用户隐藏。这可能有点令人困惑,但是对于作者而言,如果不参考一些Python的“编译时”,就很难给出这种令人赞叹的清晰描述,说明正在发生的事情是很困难的。
汉克,


我继续在我的机器上尝试此操作,并得到了不同的结果。跑了X.py,但出现错误“无法从'Y'导入名称'Y2'”。然Y.py没有问题。我使用的是Python 3.7.5,您能帮忙解释一下这里的问题吗?
xuefeng huang

18

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

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

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

解决两个应用程序之间的循环导入依赖关系时,此类参考很有用。...”


6
我知道我不应该在评论中说“谢谢”,但这已经困扰了我几个小时。谢谢你,谢谢你,谢谢你!!!
MikeyE

我同意@MikeyE。我读过几个博客和Stackoverflows试图用PonyORM来解决这个问题。在别人说这是不好的做法的地方,或者为什么要将类编码为循环的,ORM正是发生这种情况的地方。因为很多示例都将所有模型都放在同一个文件中,并且我们遵循这些示例(除了我们每个文件都使用一个模型),所以当Python无法编译时,问题还不清楚。然而,答案是如此简单。正如Mike所指出的,非常感谢。
垃圾桶


2

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

在此处输入图片说明


0

我正在使用以下内容:

from module import Foo

foo_instance = Foo()

但摆脱掉circular reference我做了以下工作,它的工作:

import module.foo

foo_instance = foo.Foo()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.