Python循环导入?

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

So i’m getting this 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

and you can see that i use the same import statement further up and it works? Is there some unwritten rule about circular importing? How do i use the same class further down the call stack?


回答 0

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

I think the answer by jpmc26, while by no means wrong, comes down too heavily on circular imports. They can work just fine, if you set them up correctly.

The easiest way to do so is to use import my_module syntax, rather than from my_module import some_object. The former will almost always work, even if my_module included imports us back. The latter only works if my_object is already defined in my_module, which in a circular import may not be the case.

To be specific to your case: Try changing entities/post.py to do import physics and then refer to physics.PostBody rather than just PostBody directly. Similarly, change physics.py to do import entities.post and then use entities.post.Post rather than just Post.


回答 1

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

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

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

When you import a module (or a member of it) for the first time, the code inside the module is executed sequentially like any other code; e.g., it is not treated any differently that the body of a function. An import is just a command like any other (assignment, a function call, def, class). Assuming your imports occur at the top of the script, then here’s what’s happening:

  • When you try to import World from world, the world script gets executed.
  • The world script imports Field, which causes the entities.field script to get executed.
  • This process continues until you reach the entities.post script because you tried to import Post
  • The entities.post script causes physics module to be executed because it tries to import PostBody
  • Finally, physics tries to import Post from entities.post
  • I’m not sure whether the entities.post module exists in memory yet, but it really doesn’t matter. Either the module is not in memory, or the module doesn’t yet have a Post member because it hasn’t finished executing to define Post
  • Either way, an error occurs because Post is not there to be imported

So no, it’s not “working further up in the call stack”. This is a stack trace of where the error occurred, which means it errored out trying to import Post in that class. You shouldn’t use circular imports. At best, it has negligible benefit (typically, no benefit), and it causes problems like this. It burdens any developer maintaining it, forcing them to walk on egg shells to avoid breaking it. Refactor your module organization.


回答 2

要了解循环依赖关系,您需要记住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)

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

To understand circular dependencies, you need to remember that Python is essentially a scripting language. Execution of statements outside methods occurs at compile time. Import statements are executed just like method calls, and to understand them you should think about them like method calls.

When you do an import, what happens depends on whether the file you are importing already exists in the module table. If it does, Python uses whatever is currently in the symbol table. If not, Python begins reading the module file, compiling/executing/importing whatever it finds there. Symbols referenced at compile time are found or not, depending on whether they have been seen, or are yet to be seen by the compiler.

Imagine you have two source files:

File X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

File Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

Now suppose you compile file X.py. The compiler begins by defining the method X1, and then hits the import statement in X.py. This causes the compiler to pause compilation of X.py and begin compiling Y.py. Shortly thereafter the compiler hits the import statement in Y.py. Since X.py is already in the module table, Python uses the existing incomplete X.py symbol table to satisfy any references requested. Any symbols appearing before the import statement in X.py are now in the symbol table, but any symbols after are not. Since X1 now appears before the import statement, it is successfully imported. Python then resumes compiling Y.py. In doing so it defines Y2 and finishes compiling Y.py. It then resumes compilation of X.py, and finds Y2 in the Y.py symbol table. Compilation eventually completes w/o error.

Something very different happens if you attempt to compile Y.py from the command line. While compiling Y.py, the compiler hits the import statement before it defines Y2. Then it starts compiling X.py. Soon it hits the import statement in X.py that requires Y2. But Y2 is undefined, so the compile fails.

Please note that if you modify X.py to import Y1, the compile will always succeed, no matter which file you compile. However if you modify file Y.py to import symbol X2, neither file will compile.

Any time when module X, or any module imported by X might import the current module, do NOT use:

from X import Y

Any time you think there may be a circular import you should also avoid compile time references to variables in other modules. Consider the innocent looking code:

import X
z = X.Y

Suppose module X imports this module before this module imports X. Further suppose Y is defined in X after the import statement. Then Y will not be defined when this module is imported, and you will get a compile error. If this module imports Y first, you can get away with it. But when one of your co-workers innocently changes the order of definitions in a third module, the code will break.

In some cases you can resolve circular dependencies by moving an import statement down below symbol definitions needed by other modules. In the examples above, definitions before the import statement never fail. Definitions after the import statement sometimes fail, depending on the order of compilation. You can even put import statements at the end of a file, so long as none of the imported symbols are needed at compile time.

Note that moving import statements down in a module obscures what you are doing. Compensate for this with a comment at the top of your module something like the following:

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

In general this is a bad practice, but sometimes it is difficult to avoid.


回答 3

对于像我一样从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,
)

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

For those of you who, like me, come to this issue from Django, you should know that the docs provide a solution: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

“…To refer to models defined in another application, you can explicitly specify a model with the full application label. For example, if the Manufacturer model above is defined in another application called production, you’d need to use:

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

This sort of reference can be useful when resolving circular import dependencies between two applications.…”


回答 4

我能够(仅)将需要该模块中对象的功能导入模块:

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

I was able to import the module within the function (only) that would require the objects from this module:

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

回答 5

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

If you run into this issue in a fairly complex app it can be cumbersome to refactor all your imports. PyCharm offers a quickfix for this that will automatically change all usage of the imported symbols as well.


回答 6

我正在使用以下内容:

from module import Foo

foo_instance = Foo()

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

import module.foo

foo_instance = foo.Foo()

I was using the following:

from module import Foo

foo_instance = Foo()

but to get rid of circular reference I did the following and it worked:

import module.foo

foo_instance = foo.Foo()