试图避免Python循环依赖的实验

时间:2013-08-09 22:18:27

标签: python circular-dependency

我有一个测试环境,试图了解如何使用import x语句导入模块而不是使用from x import y来避免python循环依赖:

test/
    __init__.py
        testing.py
    a/
        __init__.py
        m_a.py
    b/
        __init__.py
        m_b.py

这些文件包含以下内容:

testing.py:

from a.m_a import A

m_a.py:

import b.m_b
print b.m_b
class A:
    pass

m_b.py:

import a.m_a
print a.m_a
class B:
    pass

有一种我无法理解的情况:

如果我从模块m_a.pym_b.py或仅从m_b.py删除打印语句,则此功能正常,但如果m_b.py处有打印,则以下内容抛出错误:

File "testing.py", line 1, in <module>
  from a.m_a import A
File "/home/enric/test/a/m_a.py", line 1, in <module>
  import b.m_b
File "/home/enric/test/b/m_b.py", line 3, in <module>
  print a.m_a
AttributeError: 'module' object has no attribute 'm_a'

你有什么想法吗?

1 个答案:

答案 0 :(得分:1)

只有删除了打印语句才能“正常”,因为你实际上并没有做任何依赖于导入的事情。它仍然是一个破碎的循环导入。

在调试器中运行它,或者在每行之后添加print语句,您将看到会发生什么:

  • testing.py: from a.m_a import A
  • a.m_a:import b.m_b
  • b.m_b:import a.m_a
  • b.m_b:print a.m_a

在模块完成导入之前,显然正在尝试访问a.m_a。 (事实上​​,您可以在回溯中看到堆栈中剩余的a.m_a。)

如果您此时转出sys.modules,您会找到两个名为aa.m_a的部分模块,但如果您dir(a),则没有m_a还有。

据我所知,在m_a完成评估之前,a未添加到m_a.py的事实未在Python 2.7文档中的任何位置记录。 (3.x有一个更完整的导入过程规范 - 但它也是一个非常不同的导入过程。)因此,你不能依赖这个失败成功;任何一个都是完全合法的实施。 (但它至少在CPython和PyPy中失败了......)


更一般地说,使用import foo代替from foo import bar并不会神奇地解决所有循环导入问题。它只解决了一类特殊的循环导入问题(或者更确切地说,使得该类没有实际意义)。 (我意识到the FAQ中有一些误导性的文字。)


有各种各样的技巧可以解决循环导入问题,同时仍然允许您拥有循环顶级依赖项。但实际上,摆脱循环顶级依赖关系几乎总是更简单。

在这个玩具案例中,a.m_a根本没有理由完全依赖b.m_b。如果你需要打印a.m_a的一些,有更好的方法来获得它而不是完全独立的包!

在现实代码中,m_a中可能存在m_b需要的一些内容,反之亦然。但通常情况下,您可以将其分为两个级别:m_a中需要m_b的内容,以及m_a中需要m_b内容的内容。所以,只需将其拆分为两个模块。这与尝试备份和import main的一堆模块的常见修复方法完全相同:将utils分开main

如果m_b需要m_a确实存在需要m_b的内容,该怎么办?那么,在这种情况下,您可能必须插入一个间接级别。例如,也许您可​​以将事物从m_b传递到函数/构造函数/来自m_a的任何内容,因此它可以将其作为本地参数值而不是全局访问。 (没有更具体的问题,很难更具体。)

如果最糟糕的情况发生,并且您无法通过间接删除导入,则必须将导入移出。这可能再次意味着在函数调用等内部进行导入(如紧接在段落之后的常见问题解答中所解释的那样),或者只是在导入之上移动一些代码,或者各种其他可能性。但是考虑一下这些最后的解决方案,这些解决方案不能干净利落地设计,而不是你设计的路线图。