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

2024-11-22 08:47:00
admin
原创
169
摘要:问题描述:在 Python 中,当两个模块尝试互相连接时会发生什么import?更一般地,如果多个模块import循环尝试连接会发生什么?另请参阅“我该如何处理“ImportError:无法导入名称 X”或“AttributeError:...(很可能是由于循环导入)”?了解可能导致的常见问题,以及如何重写代...

问题描述:

在 Python 中,当两个模块尝试互相连接时会发生什么import?更一般地,如果多个模块import循环尝试连接会发生什么?


另请参阅“我该如何处理“ImportError:无法导入名称 X”或“AttributeError:...(很可能是由于循环导入)”?了解可能导致的常见问题,以及如何重写代码以避免此类导入的建议。请参阅为什么循环导入似乎在调用堆栈的上层工作,但随后在下层引发 ImportError?了解问题发生的原因和方式的技术细节。


解决方案 1:

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

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

Python 2 和 Python 3 中循环导入的示例

文章《Python 循环导入何时会造成致命后果? 》给出了四个示例,其中循环导入由于上述原因不会造成致命后果。

模块顶部;无来自;仅限 Python 2

# lib/foo.py                         # lib/bar.py
import bar                           import foo

def abc():                           def xyz():
    print(bar.xyz.__name__)              print(foo.abc.__name__)

模块顶部;来自 ok;相对 ok;仅限 Python 3

# lib/foo.py                         # lib/bar.py
from . import bar                    from . import foo

def abc():                           def xyz():
    print(bar.xyz.__name__)              print(abc.__name__)

模块顶部;无来自;无相关

# lib/foo.py                         # lib/bar.py
import lib.bar                       import lib.foo

def abc():                           def xyz():
    print(lib.bar.xyz.__name__)          print(lib.foo.abc.__name__)

模块底部;导入属性,而不是模块;从好

# lib/foo.py                         # lib/bar.py
def abc():                           def xyz():
    print(xyz.__name__)                  print(abc.__name__)


from .bar import xyz                 from .foo import abc

功能顶部;从好

# lib/foo.py                         # lib/bar.py
def abc():                           def xyz():
    from . import bar                    from . import foo
    print(bar.xyz.__name__)              print(foo.abc.__name__)

其他示例

上面引用的文章没有讨论明星进口。

解决方案 2:

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

导入其实很简单。只需记住以下几点:

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

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

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

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

把这些放在一起,在导入模块时你就不会感到任何意外。

解决方案 3:

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

考虑以下文件:

a.py:

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

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.xa模块初始化期间访问,您将得到一个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

解决方案 4:

令我惊讶的是,还没有人提到类型提示导致的循环导入。

如果你只是因为类型提示而导致循环导入,那么可以以一种干净的方式避免它们。

考虑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.pyFooSpecificException`

因为Foo在类型检查期间才需要,所以我们可以使用来自typingexceptions.py模块的常量安全地使其导入有条件。常量仅在类型检查期间,这使我们能够有条件地导入,从而避免循环导入错误。
需要注意的是,这样做不会在运行时在 exceptions.py 中声明,这会导致。为了避免这种情况,我们添加了将模块中的所有类型注释转换为字符串的。TYPE_CHECKING`True`Foo
Foo`NameError`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 中,未来导入不存在,因此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 撰写的《Yet another solution to dig you out of a loop import hole in Python》 。

解决方案 5:

正如其他答案所描述的,这种模式在 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 2015”中,1:54:00介绍了一种在 Python 中处理循环导入的方法:

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

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

附言:您必须以 David Beazley 的口吻阅读整篇文章。

解决方案 6:

模块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 行。运行“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",程序就完成。

解决方案 7:

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

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

解决方案 8:

这里有很多很棒的答案。虽然通常可以快速解决问题,其中一些感觉比其他方法更符合 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):
        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 原则。

解决方案 9:

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

再次强调,这不是一个永久性的修复,但可能有助于那些想要修复导入错误而又不更改太多代码的人。

干杯!

解决方案 10:

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

  1. 它执行导入的模块代码

  2. 将导入的模块添加到导入模块全局符号表中

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

循环导入并不是必须不惜一切代价避免的终极祸害。在 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)

出生地

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

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

解决方案 11:

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

import request

因此这也极有可能是一种循环导入。

解决方案:

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

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

解决方案 12:

我按照下面的方法解决了这个问题,并且运行良好,没有任何错误。考虑两个文件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 ()

py:

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

我得到的输出是

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

解决方案 13:

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

这是可行的,因为您可以导入文件b而无需 Python 执行文件中的任何导入语句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."

瞧。

解决方案 14:

当我的一个 python 文件与库同名时,就会出现此问题。例如,您有一个包“ abc ”和文件“ abc.py ”。import abc将引发循环错误。

解决方案 15:

我也遇到了这个错误。就我而言,我通过交换 import-statements 来修复它__init__.py

我的情况

初始化.py

from .b import B, C
form .a import A

py

from <module name of __init__.py> import A

class A():
  a = 42

我的解决方案

初始化.py

form .a import A
from .b import B, C

解决方案 16:

酒吧.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)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用