479

如果两个模块相互导入会发生什么?

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

4

13 回答 13

402

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

问题是当你做from foo import abc(inside bar.py) 和from bar import xyz(inside foo.py) 时。因为现在每个模块都需要另一个模块已经被导入(这样我们正在导入的名称就存在)才能被导入。

于 2009-04-14T02:03:43.453 回答
338

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

进口真的很简单。请记住以下几点:

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

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

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

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

把这些放在一起,你在导入模块时应该不会有任何意外。

于 2009-04-13T16:15:00.700 回答
117

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

考虑以下文件:

一个.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 的第二次导入(在 second 中a in),Python 解释器不会b再次导入,因为它已经存在于模块 dict 中。

如果您在模块初始化期间尝试访问b.x,您将获得一个.aAttributeError

将以下行附加到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.

于 2009-04-13T16:16:23.973 回答
45

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

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

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

大多数循环导入实际上不是逻辑循环导入,而是引发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 Beazley 的精彩演讲模块和包:生与死!- PyCon 2015 , 1:54:00, 这是一种在 python 中处理循环导入的方法:

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

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

PS:你必须用大卫比兹利的声音阅读整篇文章。

于 2015-11-05T14:51:51.887 回答
14

模块 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 行,而由于循环导入,它应该输出 infinitial。此处列出了运行“Module 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",程序将完成。
于 2018-07-06T03:16:05.577 回答
12

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

foo.py

import bar

class gX(object):
    g = 10

酒吧.py

from foo import gX

o = gX()

主文件

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
于 2009-04-14T07:30:51.730 回答
8

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

考虑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}.'

这将通过and轻松提高ImportErrorasmain.py进口exception.py,反之亦然。FooSpecificException

因为Foo仅在类型检查期间才需要,所以我们可以使用类型模块中的常量exceptions.py安全地使其有条件导入。该常量仅在类型检查期间使用,这允许我们有条件地导入,从而避免循环导入错误。 在 Python 3.6 中,使用前向引用:TYPE_CHECKINGTrueFoo

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'):  # The quotes make Foo a forward reference
       ​self.cause = cause

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

在 Python 3.7+ 中,延迟评估注释(在PEP 563中引入)允许使用“普通”类型而不是前向引用:

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 can be used in type hints without issue
       ​self.cause = cause

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

在 Python 3.11+ 中,from __future__ import annotations默认情况下是活动的,因此可以省略。

该答案基于Stefaan Lippens的另一种解决方案,可将您从 Python 中的圆形导入孔中挖掘出来。

于 2021-05-24T14:23:36.113 回答
5

这里有很多很棒的答案。虽然问题通常有快速的解决方案,其中一些感觉比其他的更 Pythonic,如果你有做一些重构的奢侈,另一种方法是分析你的代码的组织,并尝试删除循环依赖。例如,您可能会发现:

文件 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 原则。

于 2019-09-05T19:44:19.327 回答
4

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

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

干杯!

于 2017-03-02T19:11:28.470 回答
2

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

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

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

循环进口并不是不惜一切代价避免的终极邪恶。在一些像 Flask 这样的框架中,它们是非常自然的,并且调整你的代码以消除它们并不会使代码变得更好。

主文件

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
于 2018-06-30T09:57:33.653 回答
1

我通过以下方式解决了这个问题,它运行良好,没有任何错误。考虑两个文件a.pyb.py.

我添加了这个a.py并且它起作用了。

if __name__ == "__main__":
        main ()

一个.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
于 2018-10-09T12:48:24.813 回答
1

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

import request

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

解决方案:

aaa.py只需将您的测试文件更改为其他名称,例如request.py.

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

于 2020-11-10T02:21:52.713 回答
0

好的,我想我有一个很酷的解决方案。假设您有 filea和 file b。您有要在 module 中使用的一个def或一个classin 文件,但是您还有其他东西,一个,或 file 中的变量,您需要在您的定义或文件中的类。您可以做的是,在 file 的底部,在调用 file所需的 file 中的函数或类之后,但在从 file 调用 file 所需的函数或类之前,说 Then ,这是关键部分, 在文件中需要或来自文件的所有定义或类中badefclassabaabbaimport bbdefclassa(我们称之为CLASS),你说from a import CLASS

这是有效的,因为您可以b在 Python 不执行 file 中的任何导入语句的情况下导入文件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."

瞧。

于 2015-02-06T01:07:27.257 回答