我有一个测试环境来尝试了解如何使用 import x
语句导入模块,而不是使用 from x import y
来避免 python 循环依赖:
test/
__init__.py
testing.py
a/
__init__.py
m_a.py
b/
__init__.py
m_b.py
这些文件包含以下内容:
测试.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.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'
你有什么想法吗?
最佳答案
它仅在删除打印语句的情况下“起作用”,因为您实际上并没有执行任何依赖于导入的操作。它仍然是一个损坏的循环导入。
要么在调试器中运行它,要么在每行后面添加一个 print
语句,您将看到会发生什么:
- testing.py:
从 a.m_a 导入 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
。
据我所知,在 m_a.py
完成评估之前,m_a
不会添加到 a
中,这一事实并不存在记录在 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
。所以,只需将其分成两个模块即可。这实际上与尝试备份并导入 main 的一堆模块的常见修复相同:将 utils
与 main
分开.
如果 m_b
确实需要 m_a
提供某些东西,而该东西也需要 m_b
怎么办?那么,在这种情况下,您可能必须插入一个间接级别。例如,也许您可以将 thing-from-m_b
传递到 m_a
的函数/构造函数/任何内容中,这样它就可以将其作为本地参数值而不是作为一个全局性的。 (如果没有更具体的问题,就很难更具体。)
如果最坏的情况发生,并且您无法通过间接删除导入,则必须将导入移开。这可能再次意味着在函数调用中进行导入等(如常见问题解答中在让您失望的段落之后立即解释的那样),或者只是将一些代码移到导入上方,或者各种其他可能性。但请考虑这些最后的解决方案,以解决无法干净设计的问题,而不是您的设计可遵循的路线图。
关于python - 尝试避免 Python 循环依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18156500/