2014-03-05 75 views
53

所以我得到这个错误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语句进一步上涨和它的作品?有没有关于循环导入的不成文规定?我如何在调用堆栈下面继续使用同一个类?

+0

另请参见:[“Python 2和Python 3中的循环导入:它们何时致命?它们何时工作?”](https://gist.github.com/datagrok/40bf84d5870c41a77dc6)。 –

回答

77

我认为jpmc26目前公认的答案归结过于依赖进口的圆形。如果你正确设置它们,它们可以很好地工作。

这样做最简单的方法是使用import my_module语法,而不是from my_module import some_object。前者几乎总是有效的,即使my_module包括进口我们回来。如果my_object已在my_module中定义,后者仅在循环导入时可能不适用。

具体到您的案例:尝试更改entities/post.pyimport physics,然后参考physics.PostBody而不是直接PostBody。同样,将physics.py更改为import post,然后使用post.Post而不仅仅是Post

+3

此答案与相对导入兼容吗? – Joe

+5

这是为什么发生? –

+1

说非''from'语法总是能工作是错误的。如果我有'class A(object):pass; C模块中的类(b.B):pass'和模块b中的类B(a.A):pass'然后循环导入仍然是一个问题,这不起作用。 – CrazyCasta

38

当导入首次一个模块(或它的部件),该模块中的代码被顺序地与任何其他代码执行;例如,它的处理方式与函数的主体没有任何区别。一个import只是像任何其他(赋值语句,一个函数调用,defclass)命令。假设你的进口发生在脚本的顶部,那么这里发生的事情:

  • 当您尝试从world导入World,该world脚本被执行。
  • world脚本进口Field,这将导致entities.field脚本得到执行。
  • 这个过程一直持续,直到你到达entities.post脚本,因为你试图导入Post
  • entities.post脚本导致,因为它试图导入PostBody
  • 最后,physics,尝试从entities.post
  • 导入 Post要执行 physics模块
  • 我不确定entities.post模块是否存在于内存中,但它确实没关系。任一模块不在存储器中,或者该模块还没有一个Post构件,因为它尚未完成执行,以限定Post
  • 无论哪种方式,发生错误时,因为Post是不存在要导入

所以,不,它不是“工作进一步向上调用堆栈”。这是错误发生位置的堆栈跟踪,这意味着它错误地尝试导入该类中的Post。你不应该使用循环进口。最好的情况是它的收益可以忽略不计(通常,没有的好处),并且会导致这样的问题。它负担任何开发商维护它,迫使他们走在蛋壳上,以避免打破它。重构您的模块组织。

+0

是的,我认为这是这样的。它只是在该文件中的类定义,我只需要导入,所以我可以进行类检查,即'if userData == Post:': - ? – CpILL

+1

应该是'isinstance(userData,Post)'。无论如何,你没有选择。循环导入不起作用。你有循环进口的事实对我来说是一种代码味道。它表明你有一些功能应该移出到第三个模块。如果不考虑两个班级,我都说不出什么。 – jpmc26

+3

@CpILL过了一段时间,我发现了一个非常黑客的选择。如果你现在无法做到这一点(由于时间限制或你有什么),那么你*可以* *在你使用它的方法中进行本地导入。直到函数被调用时,'def'中的函数体才会被执行,所以直到你实际调用该函数才会导致导入。届时,“进口”应该起作用,因为其中一个模块在呼叫之前已经完全导入。这是一个非常令人厌恶的黑客行为,它不应该留在你的代码基础上很长时间。 – jpmc26

6

对于那些你们谁和我一样,都从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, 
) 

这种参考是有用的解决循环进口时两个应用程序之间的依赖性 ...“

+0

我知道我不应该用评论来说“谢谢”,但这一直困扰着我几个小时。谢谢你,谢谢你,谢谢你!!! – MikeyE

15

为了理解循环依赖关系,您需要记住,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中的导入语句。由于X.py已经在模块表中,因此Python使用现有的不完整的X.py符号表来满足所请求的任何引用。任何出现在X.py中的导入语句之前的符号现在都在符号表中,但之后的任何符号都不是。由于X1现在出现在导入语句之前,因此它已成功导入。然后Python继续编译Y.py.在这样做时,它定义了Y2并完成了编译Y.py.然后它恢复编译X.py,并在Y.py符号表中找到Y2。编译最终会完成没有错误。

如果尝试从命令行编译Y.py,会发生非常不同的情况。编译Y.py时,编译器在定义Y2之前触发导入语句。然后它开始编译X.py.不久它就会触发需要Y2的X.py中的import语句。但是Y2是未定义的,所以编译失败。

请注意,如果您修改X.py来导入Y1,无论您编译哪个文件,编译都会成功。但是,如果您修改文件Y.py来导入符号X2,那么这两个文件都不会编译。

任何时候模块X,或X可能会导入当前模块中引入任何模块,不要使用:

from X import Y 

你认为有可能是一个圆形的进口,你也应该避免编译时引用的任何时间到其他模块中的变量。这个模块导入X.进一步假设的Y X import语句之后被定义之前

import X 
z = X.Y 

假设模块X导入该模块:考虑无辜的看着代码。那么当这个模块被导入时,Y将不会被定义,并且你会得到一个编译错误。如果该模块首先导入Y,则可以避开它。但是当你的一个同事天真地改变第三个模块中定义的顺序时,代码就会中断。

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

请注意,在模块中向下移动导入语句会掩盖您正在执行的操作。弥补这一带在你的模块类似下面的顶部评论:

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

总的来说,这是一个不好的做法,但有时是难以避免的。

+1

我不认为Python中存在编译器或编译时间 – pkqxdd

+2

Python *不具有编译器,而*编译为@pkqxdd,编译通常隐藏于用户之外。这可能有点令人困惑,但作者很难清楚地描述发生了什么,而没有提及Python的某些模糊的“编译时间”。 – Hank