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

尝试避免Python循环依赖的实验,python,circular-dependency,Python,Circular Dependency,我有一个测试环境来尝试理解如何避免使用import x语句导入模块,而不是使用from x import y: test/ __init__.py testing.py a/ __init__.py m_a.py b/ __init__.py m_b.py 这些文件包含以下内容: 测试。py: from a.m_a import A import b.m_b print b.m_b cla

我有一个测试环境来尝试理解如何避免使用
import x
语句导入模块,而不是使用
from x import y

test/
    __init__.py
        testing.py
    a/
        __init__.py
        m_a.py
    b/
        __init__.py
        m_b.py
这些文件包含以下内容:

测试。py:

from a.m_a import A
import b.m_b
print b.m_b
class A:
    pass
import a.m_a
print a.m_a
class B:
    pass
m_a.py:

from a.m_a import A
import b.m_b
print b.m_b
class A:
    pass
import a.m_a
print a.m_a
class B:
    pass
m_b.py:

from a.m_a import A
import b.m_b
print b.m_b
class A:
    pass
import a.m_a
print a.m_a
class B:
    pass
有一种情况我无法理解:

如果我从模块
m_a.py
m_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'
文件“testing.py”,第1行,在
从a.m_a导入a
文件“/home/enric/test/a/m_a.py”,第1行,在
进口b.mÈb
文件“/home/enric/test/b/m_b.py”,第3行,在
打印上午10点
AttributeError:“模块”对象没有属性“m_a”
你有什么想法吗?

它只在删除打印语句时“起作用”,因为你实际上没有做任何依赖于导入的事情。这仍然是一个不完整的循环输入

在调试器中运行此命令,或者在每行后面添加一个
print
语句,您将看到发生了什么:

  • testing.py:
    从a.m_导入a
  • a、 m_a:
    导入b.m_b
  • b、 m_b:
    导入a.m_a
  • b、 m_b:
    打印a.m_a
很明显,它试图在模块完成导入之前访问
a.m\a
。(事实上,您可以在回溯中看到堆栈上剩余的
a.m_a

如果您在此时转储
sys.modules
,您将找到两个名为
a
a.m_a
的部分模块,但是如果您
dir(a)
,则那里还没有
m_a

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


更一般地说,使用
import foo
而不是foo import bar中的
,并不能神奇地解决所有循环导入问题。它只解决了一类特殊的循环导入问题(或者,更确切地说,使该类变得毫无意义)。(我意识到在这方面有一些误导性的文字。)


有各种各样的技巧来处理循环导入,同时仍然让您拥有循环的顶级依赖项。但实际上,摆脱循环的顶级依赖几乎总是简单的

在这个玩具案例中,
a.m_a
完全没有理由依赖于
b.m_b
。如果您需要打印出a.m_a
,有比从完全独立的软件包中获取更好的方法

在实际代码中,
mua
中可能有一些东西是
mub
需要的,反之亦然。但通常,您可以将其分为两个级别:需要
mub
mua
中的内容,以及
mub
需要的
mua
中的内容。所以,把它分成两个模块。这实际上与一组模块的常见修复方法是一样的,这些模块试图访问backup并导入main:将
utils
拆分为off
main

如果确实有
mub
需要
mua
的东西,也需要
mub
呢?那么,在这种情况下,您可能需要插入一个间接级别。例如,您可以将东西从-
m_b
传递到函数/构造函数/来自
m_a
的任何东西,这样它就可以作为局部参数值而不是全局参数值来访问它。(如果没有更具体的问题,很难说得更具体。)

如果出现最坏的情况,并且无法通过间接方式删除导入,则必须将导入移开。这也可能意味着在函数调用内部进行导入,等等(正如在引发您注意的段落之后的常见问题解答中所解释的),或者只是在导入上方移动一些代码,或者其他各种可能性。但是考虑一下这些无法解决的问题的最终解决方案,而不是你的设计遵循的路线图。

< P>它只删除了打印语句,因为你实际上没有做任何依赖于导入的事情。这仍然是一个不完整的循环输入

在调试器中运行此命令,或者在每行后面添加一个
print
语句,您将看到发生了什么:

  • testing.py:
    从a.m_导入a
  • a、 m_a:
    导入b.m_b
  • b、 m_b:
    导入a.m_a
  • b、 m_b:
    打印a.m_a
很明显,它试图在模块完成导入之前访问
a.m\a
。(事实上,您可以在回溯中看到堆栈上剩余的
a.m_a

如果您在此时转储
sys.modules
,您将找到两个名为
a
a.m_a
的部分模块,但是如果您
dir(a)
,则那里还没有
m_a

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


更一般地说,使用
import foo
而不是foo import bar中的