如何在Python中正确处理循环模块依赖?

时间:2013-12-04 17:51:36

标签: python module importerror cyclic-reference cyclic-dependency

尝试在Python中找到一个合适的模式来处理循环模块依赖。通常,解决方案是将其删除(通过重构);但是,在这种特殊情况下,我们真的希望拥有需要循环导入的功能。

编辑:根据下面的答案,这类问题的通常攻角是重构。但是,为了这个问题,假设这不是一个选项(无论出于何种原因)。

问题:

logging模块需要configuration模块来获取其某些配置数据。但是,对于某些configuration函数,我真的想使用logging模块中定义的自定义日志记录函数。显然,导入logging中的configuration模块会引发错误。

我们可以想到的可能解决方案:

  1. 不要这样做。正如我之前所说,这不是一个好的选择,除非所有其他可能性都是丑陋和糟糕的。

  2. 对模块进行Monkey-patch 。这听起来不太糟糕:在初始导入之后,在实际使用任何函数之前,将logging模块动态加载到configuration 中。这意味着定义全局的每模块变量。

  3. 依赖注入。我已阅读并尝试依赖注入替代方案(特别是在Java Enterprise空间中),它们消除了一些令人头痛的问题;然而,它们可能太复杂而无法使用和管理,这是我们想要避免的。不过,我不知道全景是如何在Python中的。

  4. 启用此功能的好方法是什么?

    非常感谢!

4 个答案:

答案 0 :(得分:4)

如前所述,可能需要进行一些重构。根据名称,如果日志记录模块使用配置,在考虑配置中应该考虑配置参数的内容时,可能没问题,那么就会出现一个问题,为什么这个配置会完全记录?

使用日志记录的配置下的代码部分可能不属于配置模块:似乎正在进行某种处理并记录结果或错误。

没有内在的知识,只使用常识,“配置”模块应该是简单的,没有太多的处理,它应该是导入树中的叶子。

希望它有所帮助!

答案 1 :(得分:3)

这对你有用吗?

# MODULE a (file a.py)
import b
HELLO = "Hello"

# MODULE b (file b.py)
try:
    import a
    # All the code for b goes here, for example:
    print("b done",a.HELLO))
except:
    if hasattr(a,'HELLO'):
        raise
    else:
        pass

现在我可以进行导入了。当循环导入(由a中的import b语句引起)抛出异常时,它会被捕获并被丢弃。当然,你的整个模块b必须缩进一个额外的块间距,你必须知道变量HELLO在a中的声明位置。

如果你不想通过插入try:except:logic来修改b.py,你可以将整个b源移动到一个新文件,称之为c.py,然后创建一个简单的文件b.py这样:

# new Module b.py
try:
    from c import *
    print("b done",a.HELLO) 
except:
    if hasattr(a,"HELLO"):
        raise
    else:
        pass

# The c.py file is now a copy of b.py:
import a
# All the code from the original b, for example:
print("b done",a.HELLO))

这会将整个命名空间从c导入到b,并且也可以通过循环导入。

我意识到这很严重,所以不要告诉任何人。

答案 2 :(得分:2)

循环模块依赖通常是代码气味。

它表示应重新考虑部分代码,以使其在两个模块外部。

答案 3 :(得分:2)

因此,如果我正确阅读您的用例,logging将访问configuration以获取配置数据。但是,configuration有一些功能,在调用时,需要logging中的configuration中的内容导入。

如果是这种情况(即configuration在开始调用函数之前并不真正需要logging),答案很简单:在configuration中,放置所有导入来自文件底部的logging,在所有类,函数和常量定义之后。

Python从上到下读取内容:当它在import中遇到configuration语句时,它会运行它,但此时configuration已经作为一个模块可以存在即使它尚未完全初始化,也要导入它:它只具有在import语句运行之前声明的属性。

我同意其他人的观点,即循环导入通常是代码味道。