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

Xol*_*lve 318 python circular-dependency cyclic-reference

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

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

小智 270

如果你做import foo内部barimport bar内部foo,它将工作正常.当任何实际运行时,两个模块将完全加载并且将相互引用.

问题是,当你不是做from foo import abcfrom bar import xyz.因为现在每个模块都需要导入其他模块(以便我们导入的名称存在)才能导入.

  • 似乎`from foo import*`和`from bar import*`也可以正常工作. (24认同)
  • @Akavall不是。那只会导入执行`import`语句时可用的名称。因此它不会出错,但是您可能无法获得所有期望的变量。 (3认同)
  • 注意,如果你从`foo import*`和`from bar import*`'执行,``foo`中执行的所有操作都处于`bar`的初始化阶段,而`bar`中的实际函数尚未定义. .. (3认同)
  • 使用 a.py/b.py 检查对上面帖子的编辑。他不使用`from x import y`,但仍然得到循环导入错误 (2认同)
  • 这不完全正确.就像import*from一样,如果您尝试在顶级导入中访问元素,那么在脚本完成运行之前,那么您将遇到相同的问题.例如,如果要在一个包中设置另一个包的全局包,并且它们彼此包含在一起.我这样做是为了在基类中为对象创建一个草率的工厂,其中该对象可以是许多子类之一,并且使用代码不需要知道它实际创建的是什么. (2认同)

Sha*_*son 262

去年在comp.lang.python上有一个非常好的讨论.它非常彻底地回答了你的问题.

进口非常简单.请记住以下内容:

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

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

如果sys.modules中存在模块,则导入只会返回该模块,无论它是否已完成执行.这就是为什么循环导入可能会返回看似部分为空的模块的原因.

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

把这些东西放在一起,导入模块时不应该有任何意外.

  • @meawoppl你能扩展一下这个评论吗?他们有多具体改变? (13认同)
  • 注意:Python3中的循环导入语义已经改变了! (8认同)
  • 他们是def.3.0-3.4不支持.或者至少成功的语义是不同的.这是我发现的概要,不提及3.5的变化.https://gist.github.com/datagrok/40bf84d5870c41a77dc6 (4认同)
  • 截至目前,python3中唯一提到循环导入"什么是新的?" 页面是[在3.5一](https://docs.python.org/3.5/whatsnew/3.5.html).它说"现在支持涉及相对进口的循环进口".@meawoppl你有没有找到这些页面中没有列出的其他内容? (2认同)

Tor*_*rek 98

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

请考虑以下文件:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"
Run Code Online (Sandbox Code Playgroud)

b.py:

print "b in"
import a
print "b out"
x = 3
Run Code Online (Sandbox Code Playgroud)

如果您执行a.py,您将获得以下内容:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
Run Code Online (Sandbox Code Playgroud)

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

如果您尝试访问b.xa模块的初始化过程中,你会得到一个AttributeError.

将以下行附加到a.py:

print b.x
Run Code Online (Sandbox Code Playgroud)

然后,输出是:

$ 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'
Run Code Online (Sandbox Code Playgroud)

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

  • 这大大解释了问题,但解决方案怎么样?我们怎么能正确导入和打印x?上面的另一个解决方案对我不起作用 (11认同)
  • 我认为如果您使用“__name__”而不是“a”,这个答案会受益匪浅。一开始我很困惑为什么一个文件会被执行两次。 (3认同)

Ite*_*iam 48

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

\n

考虑main.py哪个使用另一个文件中的异常:

\n
from src.exceptions import SpecificException\n\nclass Foo:\n    def __init__(self, attrib: int):\n        self.attrib = attrib\n\nraise SpecificException(Foo(5))\n
Run Code Online (Sandbox Code Playgroud)\n

以及专用的异常类exceptions.py

\n
from src.main import Foo\n\nclass SpecificException(Exception):\n    def __init__(self, cause: Foo):\n        self.cause = cause\n\n    def __str__(self):\n        return f\'Expected 3 but got {self.cause.attrib}.\'\n
Run Code Online (Sandbox Code Playgroud)\n

这将通过和提高ImportErrormain.py导入exception.py,反之亦然。FooSpecificException

\n

因为Foo仅在类型检查期间需要,所以我们可以使用输入模块中的常量exceptions.py安全地使其导入有条件。该常量仅在类型检查期间有效,这允许我们有条件地导入,从而避免循环导入错误。\n需要注意的是,这样做不会在运行时在 excepts.py 中声明,这会导致. 为了避免这种情况,我们添加了将模块中的所有类型注释转换为字符串的功能。TYPE_CHECKINGTrueFoo
FooNameErrorfrom __future__ import annotations

\n

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

\n
from __future__ import annotations\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:  # Only imports the below statements during type checking\n   \xe2\x80\x8bfrom src.main import Foo\n\nclass SpecificException(Exception):\n   def __init__(self, cause: Foo):  # Foo becomes \'Foo\' because of the future import\n       \xe2\x80\x8bself.cause = cause\n\n   \xe2\x80\x8bdef __str__(self):\n       \xe2\x80\x8breturn f\'Expected 3 but got {self.cause.attrib}.\'\n
Run Code Online (Sandbox Code Playgroud)\n

在 Python 3.6 中,future import 不存在,因此Foo必须是字符串:

\n
from typing import TYPE_CHECKING\nif TYPE_CHECKING:  # Only imports the below statements during type checking\n   \xe2\x80\x8bfrom src.main import Foo\n\nclass SpecificException(Exception):\n   \xe2\x80\x8bdef __init__(self, cause: \'Foo\'):  # Foo has to be a string\n       \xe2\x80\x8bself.cause = cause\n\n   \xe2\x80\x8bdef __str__(self):\n       \xe2\x80\x8breturn f\'Expected 3 but got {self.cause.attrib}.\'\n
Run Code Online (Sandbox Code Playgroud)\n

在 Python 3.5 及更低版本中,类型提示功能尚不存在。
\n在Python的未来版本中,注释功能可能会成为强制性的,之后将不再需要导入。这将发生在哪个版本中尚未确定。

\n

这个答案基于Stefaan Lippens 提出的另一个解决方案,可以帮助你摆脱 Python 中的循环导入漏洞

\n

  • 仅供参考,“from __future__ import 注解”原计划成为 3.10 中的默认设置,推迟到 3.11,并且[然后由于它产生的一些问题而被无限期搁置,而他们还没有拿出一个好的解决方案](https: //docs.python.org/3/whatsnew/3.11.html#pep-563-may-not-be-the-future)。因此,如果您想要它,您将需要一段时间的“__future__”导入。 (3认同)

Seb*_*zny 25

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

def dostuff(self):
     from foo import bar
     ...
Run Code Online (Sandbox Code Playgroud)

当其他模块导入文件时,这将避免执行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)
Run Code Online (Sandbox Code Playgroud)

应用程序B.

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()
Run Code Online (Sandbox Code Playgroud)

来自David Beazleys的优秀演讲模块和套餐:Live and Let Die!- PyCon 2015,1:54:00这是一种在python中处理循环导入的方法:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
Run Code Online (Sandbox Code Playgroud)

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

PS:你必须以David Beazley的声音阅读这整篇文章.

  • 如果已导入模块,则不会引发ImportError.可以根据需要多次导入模块,即"导入;导入a;" 没关系. (7认同)
  • 这将使其成为我实验中的“模块”而不是“类”。 (2认同)

Xol*_*lve 8

我在这里得到了一个令我印象深刻的例子!

foo.py

import bar

class gX(object):
    g = 10
Run Code Online (Sandbox Code Playgroud)

bar.py

from foo import gX

o = gX()
Run Code Online (Sandbox Code Playgroud)

main.py

import foo
import bar

print "all done"
Run Code Online (Sandbox Code Playgroud)

在命令行: $ 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
Run Code Online (Sandbox Code Playgroud)

  • 呃......我想我用这个令人难以置信的丑陋黑客修复了我的问题.{{{如果不是'foo.bar'在sys.modules中:来自foo import bar else:bar = sys.modules ['foo.bar']}}}就个人而言,我认为循环导入是一个巨大的警告标志,代码不好设计... (11认同)
  • @ c089,或者你可以将`foo.py`中的`import bar`移到最后 (5认同)
  • 如果`bar`和`foo`都必须使用`gX`,那么'最干净'的解决方案是将`gX`放在另一个模块中并让`foo`和`bar`导入该模块.(在没有隐藏的语义依赖性的意义上最干净.) (4认同)
  • 你是怎么解决这个问题的?我正在尝试理解循环导入以解决我自己的一个问题,该问题看起来与您正在做的事情*非常* 相似... (2认同)
  • 蒂姆说得很好。基本上这是因为 `bar` 甚至无法在 foo.h 中找到 `gX`。循环导入本身很好,但只是在导入时没有定义 `gX`。 (2认同)

Moh*_*adi 6

模块a.py:

import b
print("This is from module a")
Run Code Online (Sandbox Code Playgroud)

模块b.py

import a
print("This is from module b")
Run Code Online (Sandbox Code Playgroud)

运行“模块a”将输出:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 
Run Code Online (Sandbox Code Playgroud)

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

  1. 第一行是import b。因此它将访问模块b
  2. 模块b的第一行是import a。因此它将访问模块a
  3. import b但是模块a的第一行是,但是请注意,该行将不再执行,因为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",程序将完成。

  • 发生这种情况可能只是因为`a.py`,当*作为脚本执行*时,将被命名为“模块`__main__`”,**不是**“模块`a`”。因此,当它到达“b”并遇到“import a”时,现在它将在不同的“模块名称”下导入相同的“文件”,对吧?当`__main__`脚本都不是时会发生什么? (2认同)

hlo*_*ore 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)
Run Code Online (Sandbox Code Playgroud)

文件 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))
Run Code Online (Sandbox Code Playgroud)

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

文件 c.py

def save_result(result):
    print('save the result')
Run Code Online (Sandbox Code Playgroud)

将允许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)
Run Code Online (Sandbox Code Playgroud)

重构文件 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))
Run Code Online (Sandbox Code Playgroud)

总之,如果您有一个工具(例如 pylint 或 PyCharm)报告可以是静态的方法,那么仅仅staticmethod在它们上抛出装饰器可能不是消除警告的最佳方法。尽管该方法似乎与类相关,但最好将其分开,特别是如果您有几个可能需要相同功能的密切相关的模块,并且您打算实践 DRY 原则。