Python中的循环(或循环)导入-Python 实用宝典

Python中的循环(或循环)导入

如果两个模块相互导入会怎样? 为了概括这个问题,Python中的循环导入怎么办?

问题:Python中的循环(或循环)导入

如果两个模块相互导入会怎样?

为了概括这个问题,Python中的循环导入怎么办?

What will happen if two modules import each other?

To generalize the problem, what about the cyclic imports in Python?


回答 0

去年在comp.lang.python上对此进行了非常好的讨论。它相当彻底地回答了您的问题。

导入确实非常简单。只要记住以下几点:

'import'和'from xxx import yyy'是可执行语句。它们在运行的程序到达该行时执行。

如果模块不在sys.modules中,则导入将在sys.modules中创建新的模块条目,然后在模块中执行代码。在执行完成之前,它不会将控制权返回给调用模块。

如果sys.modules中确实存在某个模块,则无论导入是否完成执行,导入都会简单地返回该模块。这就是循环导入可能返回部分为空的模块的原因。

最后,执行脚本在名为__main__的模块中运行,以其自己的名称导入脚本将创建一个与__main__无关的新模块。

放在一起,导入模块时就不会感到惊讶。

There was a really good discussion on this over at comp.lang.python last year. It answers your question pretty thoroughly.

Imports are pretty straightforward really. Just remember the following:

'import' and 'from xxx import yyy' are executable statements. They execute when the running program reaches that line.

If a module is not in sys.modules, then an import creates the new module entry in sys.modules and then executes the code in the module. It does not return control to the calling module until the execution has completed.

If a module does exist in sys.modules then an import simply returns that module whether or not it has completed executing. That is the reason why cyclic imports may return modules which appear to be partly empty.

Finally, the executing script runs in a module named __main__, importing the script under its own name will create a new module unrelated to __main__.

Take that lot together and you shouldn't get any surprises when importing modules.


回答 1

如果在import foo内部barimport bar内部进行操作foo,它将正常工作。到实际运行任何东西时,两个模块都将完全加载,并且将相互引用。

问题是当您改为from foo import abc和时from bar import xyz。因为现在每个模块都需要先导入另一个模块(这样才能导入我们要导入的名称),然后才能导入它。

If you do import foo inside bar and import bar inside foo, it will work fine. By the time anything actually runs, both modules will be fully loaded and will have references to each other.

The problem is when instead you do from foo import abc and from bar import xyz. Because now each module requires the other module to already be imported (so that the name we are importing exists) before it can be imported.


回答 2

循环导入会终止,但是您需要注意不要在模块初始化期间使用循环导入的模块。

考虑以下文件:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

如果执行a.py,您将获得以下信息:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

在第二次导入b.py时(在第二个中a in),Python解释器不会b再次导入,因为它已经存在于模块dict中。

如果您尝试在模块初始化期间b.x从中进行访问a,则会显示AttributeError

将以下行添加到a.py

print b.x

然后,输出为:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

这是因为模块是在导入时执行的,并且在b.x访问时间时,该行x = 3尚未执行,只有在之后才执行b out

Cyclic imports terminate, but you need to be careful not to use the cyclically-imported modules during module initialization.

Consider the following files:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

If you execute a.py, you'll get the following:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

On the second import of b.py (in the second a in), the Python interpreter does not import b again, because it already exists in the module dict.

If you try to access b.x from a during module initialization, you will get an AttributeError.

Append the following line to a.py:

print b.x

Then, the output is:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

This is because modules are executed on import and at the time b.x is accessed, the line x = 3 has not be executed yet, which will only happen after b out.


回答 3

正如其他答案所描述的那样,这种模式在python中是可以接受的:

def dostuff(self):
     from foo import bar
     ...

当其他模块导入文件时,这将避免执行import语句。仅当存在逻辑循环依赖关系时,这才会失败。

大多数循环导入实际上不是逻辑循环导入,而是引发ImportError错误,因为import()调用时会评估整个文件的顶级语句。

ImportErrors如果您确实希望进口货物居于首位,则几乎可以避免这些情况

考虑以下循环导入:

应用程式A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

应用程式B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

大卫·比兹利(David Beazleys)精彩演讲模块和软件包:活着,让自己死!-PyCon 20151:54:00这是在python中处理循环导入的一种方法:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

这会尝试导入SimplifiedImageSerializer,如果ImportError引发,因为已经被导入,它将从importcache中将其拉出。

PS:您必须以David Beazley的声音阅读整篇文章。

As other answers describe this pattern is acceptable in python:

def dostuff(self):
     from foo import bar
     ...

Which will avoid the execution of the import statement when the file is imported by other modules. Only if there is a logical circular dependency, this will fail.

Most Circular Imports are not actually logical circular imports but rather raise ImportError errors, because of the way import() evaluates top level statements of the entire file when called.

These ImportErrors can almost always be avoided if you positively want your imports on top:

Consider this circular import:

App A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

App B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

From David Beazleys excellent talk Modules and Packages: Live and Let Die! - PyCon 2015, 1:54:00, here is a way to deal with circular imports in python:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

This tries to import SimplifiedImageSerializer and if ImportError is raised, because it already is imported, it will pull it from the importcache.

PS: You have to read this entire post in David Beazley's voice.


回答 4

我这里有一个让我印象深刻的例子!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

在命令行中: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

I got an example here that struck me!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

At the command line: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

回答 5

模块a.py:

import b
print("This is from module a")

模块b.py

import a
print("This is from module b")

运行“模块a”将输出:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

它输出这3行,而由于循环导入而应该输出无穷大。下面列出了在运行“模块a”时逐行发生的情况:

  1. 第一行是 import b。因此它将访问模块b
  2. 模块b的第一行是 import a。因此它将访问模块a
  3. 模块a的第一行是,import b但是请注意,此行将不再执行,因为python中的每个文件仅执行一次导入行,因此无论在何时何地执行都无关紧要。因此它将传递到下一行并打印"This is from module a"
  4. 从模块b访问完整个模块a后,我们仍在模块b中。所以下一行会打印"This is from module b"
  5. 模块b行完全执行。因此,我们将回到模块b的起始模块a。
  6. import b行已经执行,将不再执行。下一行将打印"This is from module a",程序将完成。

Module a.py :

import b
print("This is from module a")

Module b.py

import a
print("This is from module b")

Running "Module a" will output:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

It output this 3 lines while it was supposed to output infinitival because of circular importing. What happens line by line while running"Module a" is listed here:

  1. The first line is import b. so it will visit module b
  2. The first line at module b is import a. so it will visit module a
  3. The first line at module a is import b but note that this line won't be executed again anymore, because every file in python execute an import line just for once, it does not matter where or when it is executed. so it will pass to the next line and print "This is from module a".
  4. After finish visiting whole module a from module b, we are still at module b. so the next line will print "This is from module b"
  5. Module b lines are executed completely. so we will go back to module a where we started module b.
  6. import b line have been executed already and won't be executed again. the next line will print "This is from module a" and program will be finished.

回答 6

我完全同意pythoneer的回答。但是我偶然发现了一些有循环导入缺陷的代码,并在尝试添加单元测试时引起了问题。因此,在不做任何更改的情况下快速修补它,可以通过动态导入解决问题。

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

同样,这不是永久性的修复,但是可以帮助想要修复导入错误而无需更改太多代码的人。

干杯!

I completely agree with pythoneer's answer here. But I have stumbled on some code that was flawed with circular imports and caused issues when trying to add unit tests. So to quickly patch it without changing everything you can resolve the issue by doing a dynamic import.

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Again, this isn't a permanent fix but may help someone that wants to fix an import error without changing too much of the code.

Cheers!


回答 7

这里有很多很好的答案。尽管通常可以快速解决问题,但有些解决方案比其他解决方案更具有Python风格,但如果您愿意进行一些重构,则另一种方法是分析代码的组织,并尝试消除循环依赖。例如,您可能会发现您具有:

档案a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

文件b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

在这种情况下,只需将一个静态方法移至单独的文件,例如c.py

文件c.py

def save_result(result):
    print('save the result')

将允许save_result从A中删除方法,从而允许从b中的a中删除A的导入:

重构文件a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

重构文件b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

总而言之,如果您有一个报告静态方法的工具(例如pylint或PyCharm),则仅在其上添加staticmethod修饰符可能不是使警告消失的最佳方法。即使该方法似乎与该类有关,也最好将其分开,尤其是当您有几个紧密相关的模块可能需要相同的功能并且打算实践DRY原理时。

There are a lot of great answers here. While there are usually quick solutions to the problem, some of which feel more pythonic than others, if you have the luxury of doing some refactoring, another approach is to analyze the organization of your code, and try to remove the circular dependency. You may find, for example, that you have:

File a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

File b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

In this case, just moving one static method to a separate file, say c.py:

File c.py

def save_result(result):
    print('save the result')

will allow removing the save_result method from A, and thus allow removing the import of A from a in b:

Refactored File a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Refactored File b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

In summary, if you have a tool (e.g. pylint or PyCharm) that reports on methods that can be static, just throwing a staticmethod decorator on them might not be the best way to silence the warning. Even though the method seems related to the class, it might be better to separate it out, especially if you have several closely related modules that might need the same functionality and you intend to practice DRY principles.


回答 8

循环导入可能会造成混淆,因为导入有两件事:

  1. 它执行导入的模块代码
  2. 将导入模块添加到导入模块全局符号表中

前者仅执行一次,而后者在每个import语句中执行。当导入模块使用部分执行代码的已导入模块时,循环导入会产生情况。因此,它将不会看到import语句之后创建的对象。下面的代码示例对此进行了演示。

循环进口并不是不惜一切代价避免的最终罪恶。在诸如Flask之类的某些框架中,它们是很自然的,调整您的代码以消除它们并不能使代码变得更好。

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

带有注释的python main.py输出

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

Circular imports can be confusing because import does two things:

  1. it executes imported module code
  2. adds imported module to importing module global symbol table

The former is done only once, while the latter at each import statement. Circular import creates situation when importing module uses imported one with partially executed code. In consequence it will not see objects created after import statement. Below code sample demonstrates it.

Circular imports are not the ultimate evil to be avoided at all cost. In some frameworks like Flask they are quite natural and tweaking your code to eliminate them does not make the code better.

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

python main.py output with comments

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

回答 9

我通过以下方式解决了问题,并且工作正常,没有任何错误。考虑两个文件a.pyb.py

我添加到a.py它,它的工作。

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

我得到的输出是

>>> b out 
>>> a out 
>>> 5

I solved the problem the following way, and it works well without any error. Consider two files a.py and b.py.

I added this to a.py and it worked.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

The output I get is

>>> b out 
>>> a out 
>>> 5

回答 10

好的,我想我有一个很酷的解决方案。假设您有file a和file b。你有一个def或一个class文件b要在模块来使用a,但你有别的东西,无论是一个defclass或者从文件变量a您在文件中定义或类需要b。您可以做的是,在文件底部a,在调用文件a中所需的文件中b的函数或类之后,但是在从文件b中调用所需的文件中调用函数或类之前a,说import b 然后,这是关键部分,在文件b中所有需要the defor classfrom file 的定义或类中a(叫它CLASS),你说from a import CLASS

之所以可行,是因为您可以导入文件b而无需Python在file中执行任何import语句b,因此可以避免任何循环导入。

例如:

档案a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

文件b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Ok, I think I have a pretty cool solution. Let's say you have file a and file b. You have a def or a class in file b that you want to use in module a, but you have something else, either a def, class, or variable from file a that you need in your definition or class in file b. What you can do is, at the bottom of file a, after calling the function or class in file a that is needed in file b, but before calling the function or class from file b that you need for file a, say import b Then, and here is the key part, in all of the definitions or classes in file b that need the def or class from file a (let's call it CLASS), you say from a import CLASS

This works because you can import file b without Python executing any of the import statements in file b, and thus you elude any circular imports.

For example:

File a:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

File b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Voila.


本文由 Python 实用宝典 作者:Python实用宝典 发表,其版权均为 Python 实用宝典 所有,文章内容系作者个人观点,不代表 Python 实用宝典 对观点赞同或支持。如需转载,请注明文章来源。
11

抱歉,评论已关闭!