1

我有一个测试环境来尝试了解如何避免 python 循环依赖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

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'

你有什么想法?

4

1 回答 1

1

它仅在删除打印语句的情况下“起作用”,因为您实际上并没有做任何依赖于导入的事情。它仍然是一个损坏的循环导入。

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

  • 测试.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此时转储,您会发现两个名为aand的部分模块a.m_a,但如果您dir(a)是 ,则m_a还没有。

据我所知,在 Python 2.7 文档中的任何地方都没有记录在完成评估之前m_a不会添加的事实。(3.x 对导入过程有更完整的规范——但它也是一个非常不同的导入过程。)因此,无论失败还是成功,您都不能依赖它。任何一个对于实施都是完全合法的。(但它碰巧至少在 CPython 和 PyPy 中失败了……)am_a.py


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


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

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

在现实生活中的代码中,可能有一些需要的东西,m_a反之亦然m_b。但通常,您可以将其分为两个层次:m_a需要的东西和需要的m_b东西。因此,只需将其拆分为两个模块。这与一堆试图备份的模块的常见修复实际上是一样的:拆分一个关闭。m_am_bimport mainutilsmain

如果真的有东西m_b需要m_a,那也需要m_b呢?那么,在这种情况下,您可能必须插入一个间接级别。例如,也许您可​​以将 thing-from- 传递给m_bfunction/constructor/whatever from m_a,因此它可以将其作为本地参数值而不是全局参数值来访问。(如果没有更具体的问题,很难更具体。)

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

于 2013-08-09T22:32:14.690 回答