当Python循环导入不可避免时,最佳解决方案是什么?

2024-06-25 06:55:43 发布

您现在位置:Python中文网/ 问答频道 /正文

我第一次遇到这个问题是在一个更大的现有图书馆的背景下,这些图书馆急需重组。然而,现在不是时候,一个问题已经出现后,最近一系列的变化。我创建了一个抽象的例子,它与真实的库有着完全相同的问题。在

我们有以下文件结构:

|- pack
|  |- sub_a
|  |  |- __init__.py
|  |  |- a_func.py
|  |- sub_b
|  |  |- __init__.py
|  |  |- b_func.py
|  |- __init__.py
|- runit.py

主程序是runit.py,它从pack包中导入所需的特定内容并运行它。在

runit.py

^{pr2}$

包包由其余文件组成,以pack\__init__.py开头:

^{3}$

pack\sub_a\__init__.py

from .a_func import fa, fab

pack\sub_a\a_func.py

from ..sub_b import fb


def fa():
    return 0


def fab():
    return fb()

pack\sub_b\__init__.py

from .b_func import fb, fba

pack\sub_b\b_func.py

from ..sub_a import fa


def fb():
    return 1


def fba():
    return fa()

现在的问题是:如果你试图逃跑运行它.py,失败的原因是:

Traceback (most recent call last):
  File "C:/adir/runit.py", line 1, in <module>
    import pack
  File "C:\adir\pack\__init__.py", line 1, in <module>
    from .sub_a import fa, fab
  File "C:\adir\pack\sub_a\a_func.py", line 1, in <module>
    from ..sub_b import fb
  File "C:\adir\pack\sub_b\__init__.py", line 1, in <module>
    from .b_func import fb, fba
  File "C:\adir\pack\sub_b\b_func.py", line 1, in <module>
    from ..sub_a import fa
ImportError: cannot import name 'fb' 'fa' from 'pack.sub_a' (C:\adir\pack\sub_a\__init__.py)

但是,如果您删除fa导入(并在fba()中更改使用它的行),它仍然可以工作,尽管a_func.py中的fb的导入相同。在

原因是sub_a需要来自sub_b的东西,而{}需要来自{}的东西,所以创建的是循环导入。在

当然,在这个例子中,循环导入是愚蠢的。最简单的解决方案是将所有函数放在一个位置;然后,由于函数之间没有循环依赖关系,因此没有问题。在

但在实际情况下,有些循环引用是有充分理由的(我说“有点”是因为函数没有实际的循环依赖关系,但当然Python直到运行时才知道这一点)。在

我看到的唯一选择是:

  1. 把一切放在一个地方
  2. 重构模块,使所有其他模块所需的代码位于一个独立的模块中,该模块不依赖于其他模块;在本例中,这将是fa()和/或{}
  3. 将import语句移动到需要的位置,而不是顶部;在本例中,它将移动到fba()定义的内部
  4. 重构实际的函数/类以依赖依赖注入;在本例中,fba()可以有一个callback参数
  5. 为模块添加初始化方法。在

我不喜欢前四个:选项1。成为一个痛苦的阅读维护。选项2。这意味着,当前编写得相当好并且在一起很有意义的代码将需要分成两个或多个部分。选项3。看起来很像反模式,因为这使得很难看到任何模块有什么依赖关系。在选项4中,现有函数的调用约定会全面改变。在

我最初担心的是选项3。模块主体中的任何代码都将在每次导入时执行;但是@a_guest指出“模块被缓存(在sys.modules)中,并且在需要时将从缓存中获取,而不是再次执行模块的代码”

我倾向于使用选项5。在这种情况下,重写pack\sub_b\b_func.py,如下所示:

def initialise():
    global fa
    from pack import fa as pack_fa
    fa = pack_fa


def fb():
    return 0


def fba():
    return fa()

更新pack\sub_b\__init__.py

from .b_func import fb, fba, initialise

然后pack\__init__.py变成:

from .sub_a import fa, fab
from .sub_b import fb, fba

for module in [sub_a, sub_b]:
    if hasattr(module, 'initialise'):
        module.initialise()

通过在实际调用关闭循环的函数之前完成所有导入,这就解决了这个问题,因为函数内部没有实际的循环,所以不会引起冲突。在

这里的缺点是pack的__init__.py现在意识到了初始化sub_b模块的必要性,并且该模块本身有一些额外的代码,这些代码只是作为解决这个问题的一种解决办法,但是所有其他代码都与预期的一样工作(新的和遗留的)。在

只要任何导入sub_b的人在使用其内容之前总是至少调用initialise一次,它甚至可以在pack之外使用,前提是有正确的依赖关系。在

我的第六个问题是,有更好的选择吗?

注意:请注意,我不是在寻找什么是更好的意见,尽管如果你有一个强有力的、理性的论据来解释为什么这些选择中的一个必须被认为是更优越的,它可能会回答我的问题,但我真的在寻找一个更好的选择,我只是在这里错过了。我不是在为这种类型的代码设计辩护——它本应该被避免的,但事实并非如此。

免责声明:我知道关于Python中循环依赖和循环导入的其他问题,但我不认为我的问题会重复这些问题,因为没有一个深入到森林中并提出最后一个问题


Tags: 模块函数代码frompyimportfbreturn