使用相互导入或循环导入时会发生什么?

在Python中,当两个模块试图彼此import时会发生什么?更一般地说,如果多个模块试图在一个循环中import会发生什么?


另见我该如何处理&;ImportError:不能导入名称x &;或“AttributeError:…”(很可能是循环导入)"?可能导致的常见问题,以及如何重写代码以避免此类导入的建议。见为什么循环导入似乎在调用堆栈中进一步工作,但随后在进一步向下抛出ImportError ?关于为什么和如何问题发生的技术细节。

344813 次浏览

去年在comp.lang.python上有一个关于这个问题的很好的讨论。它很彻底地回答了你的问题。

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

'import'和'from XXX import yyy'是可执行语句。他们执行

.当运行的程序到达这一行时

如果模块不在sys. sys. 0中。模块,然后导入创建新模块 “sys.”模块,然后执行模块中的代码。它没有 将控制权返回给调用模块,直到执行完成

如果系统中存在模块。模块,然后导入简单地返回它 模块,不管它是否已经完成执行。这就是原因 循环导入可能会返回部分为空的模块 最后,正在执行的脚本运行在名为__main__的模块中,导入 脚本以自己的名称创建一个与。无关的新模块 __main__ . < / p > 把这些加在一起,你在进口时应该不会有任何惊喜 模块。< / p >

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

考虑以下文件:

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中。

如果你在模块初始化期间试图从a访问b.x,你将得到一个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之后。

如果你做import foo(在bar.py内)和import bar(在foo.py内),它会正常工作。在实际运行任何东西时,两个模块都将完全加载,并将相互引用。

问题是当你执行from foo import abc(在bar.py内)和from bar import xyz(在foo.py内)时。因为现在每个模块都需要另一个模块已经被导入(以便导入的名称存在),然后才能导入它。

这里有个例子让我震惊!

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
好吧,我想我有一个很酷的解决方案。 假设你有文件a和文件b。 你在文件b中有一个defclass,你想在模块a中使用,但是你有其他东西,要么是defclass,要么是文件a中的变量,你需要在你的定义或文件b中的类中使用。 你可以做的是,在文件a的底部,在调用文件a中需要的函数或类之后,但在调用文件a中需要的函数或类之前,说import b 然后,这里是关键部分,在文件b中所有需要文件a中的defclass的定义或类中(让我们称其为CLASS),你说from a import CLASS

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

例如:

文件:

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."

瞧。

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

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

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

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

如果你确实希望你的导入放在最上面,这些ImportErrors几乎总是可以避免的:

考虑这个循环导入:

应用一个

# 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的声音来阅读整篇文章。

我完全同意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")

同样,这不是一个永久性的修复,但可以帮助那些希望在不修改太多代码的情况下修复导入错误的人。

干杯!

循环导入可能令人困惑,因为导入做了两件事:

  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"

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输出带有注释

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

模块a.py:

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

模块b.py

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

运行“Module 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的行被完全执行。回到模块a,从模块b开始的地方。
  6. 导入b行已经执行,不会再次执行。下一行将打印"This is from module a",程序将完成。
我用下面的方法解决了这个问题,没有任何错误。 考虑两个文件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

这里有很多很棒的答案。虽然通常有快速解决问题的解决方案,其中一些比其他的更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')

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

重构文件a.py

from b import B
from c import save_result


class A:
@staticmethod
def do_something_a_ish(param):
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原则。

假设你正在运行一个名为request.py的测试python文件 在request.py中,您写入

import request

所以这也很可能是一个循环导入。

解决方案:

只需将测试文件更改为另一个名称,例如aaa.py,而不是request.py

不要使用其他库已经使用过的名称。

令我惊讶的是,还没有人提到由类型提示引起的循环导入。
如果类型提示导致循环导入只有,则可以以干净的方式避免它们

考虑main.py,它使用了来自另一个文件的异常:

from src.exceptions import SpecificException


class Foo:
def __init__(self, attrib: int):
self.attrib = attrib


raise SpecificException(Foo(5))

专用异常类exceptions.py:

from src.main import Foo


class SpecificException(Exception):
def __init__(self, cause: Foo):
self.cause = cause


def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'

这将引发ImportError,因为main.py导入了exception.py,反之亦然,通过FooSpecificException

因为Foo只在exceptions.py中类型检查时需要,所以我们可以使用打字模块中的TYPE_CHECKING常量安全地将其导入为有条件的。在类型检查期间常量仅为True,这允许我们有条件地导入Foo,从而避免循环导入错误。
需要注意的是,通过这样做,Foo没有在运行时的exceptions.py中声明,这将导致NameError. py中出现NameError. py。为了避免这种情况,我们添加了from __future__ import annotations,它将模块中的所有类型注释转换为字符串

因此,我们得到以下Python 3.7+的代码:

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:  # Only imports the below statements during type checking
​from src.main import Foo


class SpecificException(Exception):
def __init__(self, cause: Foo):  # Foo becomes 'Foo' because of the future import
​self.cause = cause


​def __str__(self):
​return f'Expected 3 but got {self.cause.attrib}.'

在Python 3.6中,future导入不存在,因此Foo必须是一个字符串:

from typing import TYPE_CHECKING
if TYPE_CHECKING:  # Only imports the below statements during type checking
​from src.main import Foo


class SpecificException(Exception):
​def __init__(self, cause: 'Foo'):  # Foo has to be a string
​self.cause = cause


​def __str__(self):
​return f'Expected 3 but got {self.cause.attrib}.'
在Python 3.5及以下版本中,类型提示功能还不存在。
在Python的未来版本中,注解特性可能会成为强制性的,之后就不再需要导入了。这将发生在哪个版本尚未确定。

这个答案是基于Stefaan Lippens的这是另一个解决方案,可以让您摆脱Python中的循环导入漏洞

我们喜欢在Python中使用模块,为什么不呢,它们提供了额外的功能,更好的编码实践,并且在使用时总是创建适当的结构。但很多时候,如果我们不小心将另一个文件命名为模块名,就会在不知不觉中遇到python循环导入问题。由于python倾向于先从本地当前目录导入,然后再从站点包导入,这将产生一个循环导入问题。

通常,当您不小心将工作文件的名称与模块名称相同,并且这些模块相互依赖时,就会发生Python循环导入问题。通过这种方式,python打开相同的文件,导致循环循环并最终抛出错误。

例如,当您将文件命名为random.py并尝试导入“from random import randint”时,它将抛出一个循环导入错误(也称为from partial initialized module)。

bar.py

print('going to import foo')
from foo import printx

foo.py

print('trying to import bar')
import bar


def printx():
print('x')
$ python bar.py


going to import foo
trying to import bar
going to import foo
Traceback (most recent call last):
File "bar.py", line 2, in <module>
from foo import printx
File "/home/suhail/Desktop/temp/circularimport/foo.py", line 2, in <module>
import bar
File "/home/suhail/Desktop/temp/circularimport/bar.py", line 2, in <module>
from foo import printx
ImportError: cannot import name 'printx' from partially initialized module 'foo' (most likely due to a circular import) (/home/suhail/Desktop/temp/circularimport/foo.py)