【问题标题】:Experiment trying to avoid Python circular dependencies尝试避免 Python 循环依赖
【发布时间】:2013-08-09 22:18:27
【问题描述】:

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

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'

你有什么想法吗?

【问题讨论】:

  • 旁注:如果您打算在test 目录中以python testing.py 运行它,那么您绝对不应该在test 目录中有__init__.py 文件。我不认为这与你的问题有关,但它仍然是一个糟糕的科。
  • 真的吗?我不知道这个。将test 作为一个包有什么问题?同样的错误发生在同样的情况下。
  • 您永远不应该将包或包内的任何目录作为您的 PWD 或 sys.path 上的任何位置运行。这在文档中的某处进行了解释,但我不记得在哪里。无论如何,最大的问题是您最终可能会以两个不同的名称使用同一模块的两个副本(因此您的全局变量实际上不是全局的,等等)。 (还有一个事实是,即使你弄错了,这些东西似乎也能奏效。)
  • @abarnert - 这对于具有多个级别的包来说是正常的。关于循环依赖的问题。
  • @Caumons,m_a 没有从import b.m_b 语句返回,甚至在 m_b 尝试使用它时还没有创建类 A。

标签: python circular-dependency


【解决方案1】:

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

要么在调试器中运行它,要么在每一行之后添加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.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 的内容。因此,只需将其拆分为两个模块。这实际上与一堆试图备份的模块的常见修复相同,import main:将utilsmain 分开。

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

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

【讨论】:

  • 我不是导入过程如何与 Python 一起工作的专家,我读到以这种方式导入模块是有效的(事实上,在某些情况下似乎确实如此)。那么,您有什么建议来解决问题而不必在文件末尾移动其中一个导入?有更好的选择吗?还是我用这种方法做错了什么?
  • 有多种方法可以解决这个问题,所有方法都在您可能阅读的同一个常见问题解答条目中进行了描述。将imports 之一移到末尾;将其中一个移出全局范围;将 access 移出全局范围;等等。但实际上,最好的解决方案是重新组织你的代码,这样你就没有循环的顶级依赖关系。我将编辑答案以进一步解释。
  • 好的,假设模块m_a 依赖于m_b,但是m_b 需要访问m_a 中的类中的类变量。这是我的情况,因为我需要导入类来访问它的变量成员,所以我收到了错误。
  • 如果您尝试在m_b.py 末尾添加行A = a.m_a.A,您将收到错误:AttributeError: 'module' object has no attribute 'm_a'
  • 根据墨菲定律,您可以摆脱没有好处的事情,但无法摆脱您真正想做的事情……更严重的是,了解循环依赖会发生什么,以及某些事情何时以及为什么有效,非常困难,而且通常只专注于打破循环比试图弄清楚发生了什么更容易......
猜你喜欢
  • 2013-02-06
  • 1970-01-01
  • 2012-02-15
  • 2012-08-10
  • 1970-01-01
  • 2017-05-24
  • 2013-04-02
  • 2011-06-16
  • 2023-03-11
相关资源
最近更新 更多