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

9
尝试找到一个好的和适当的模式来处理Python中的循环依赖模块。通常,解决方案是将其删除(通过重构);但是,在这种特殊情况下,我们真的希望拥有需要循环导入的功能。
编辑:根据下面的答案,这种问题的常规解决方法是重构。但是,出于这个问题的缘故,请假设这不是一个选项(无论出于什么原因)。
问题是logging模块需要configuration模块来获取一些配置数据。然而,对于一些configuration函数,我真的想使用定义在logging模块中的自定义logging函数。显然,在configuration中导入logging模块会引发错误。
我们能想到的可能的解决方案:
1. 不要这样做。正如我之前所说,这不是一个好的选择,除非所有其他可能性都丑陋且不好。 2. Monkey-patch模块。这听起来不错:在初始导入后,在任何函数实际使用之前将logging模块动态加载到configuration中。这意味着定义全局的逐模块变量。 3. 依赖注入。我已经阅读并遇到了依赖注入替代方案(特别是在Java Enterprise空间),它们消除了一些头疼的问题。但是,它们可能过于复杂,难以使用和管理,这是我们想避免的。我不知道Python中的情况如何。
有什么好的方法可以启用此功能?
非常感谢!

2
将共享的位放入它自己的(第三个)文件中,并将其导入到另外两个文件中。 - Joran Beasley
把导入语句放在“if True:”语句中会起作用吗? - jcfollower
@JoranBeasley 这是最受欢迎的答案。很有可能这就是我们最终要做的事情。谢谢。 - Juan Carlos Coto
@jcfollower 是的,很可能。那有点像猴子补丁,不是吗?谢谢! - Juan Carlos Coto
4个回答

4
正如已经提到的,可能需要进行一些重构。根据名称,如果日志记录模块使用配置,则可能是可以接受的,当考虑哪些内容应该包含在配置中时,人们会考虑配置参数,然后就会出现一个问题,为什么这个配置要用于日志记录呢?
很有可能,在使用日志记录的代码部分中,不属于配置模块的部分正在执行某种处理,并记录结果或错误。
没有内部知识,只使用常识,“配置”模块应该是一些简单的东西,没有太多处理,并且它应该是导入树中的一个叶子节点。
希望这能帮助你!

谢谢,我同意。然而,我是以“假设这不可能”的方式来询问的,以确定可能的替代方案。 - Juan Carlos Coto
1
@JuanCarlosCoto:从技术上讲,它永远不会是“不可能的”。唯一的“不可能”的情况要么是你不想做,要么是你的老板不想做。 - slebetman
@slebetman 没错-但有时候需要进行权衡。如果存在一个简单的解决方案可以使代码更易读,或者有一种方式可以提高可维护性,那么实现它可能是一个好主意。无论如何,这个问题的主要目的是获得有见地,经验丰富的开发人员的意见,他们很可能会有很棒的建议。例如,我认为有趣的是,还没有人提出依赖注入的解决方案-这是我没有预料到的。感谢您的输入,我完全同意“永远不可能”的说法。 - Juan Carlos Coto

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

现在我可以导入b模块。当由于a中的导入b语句引起循环导入时,会抛出异常并被捕获和丢弃。当然,整个b模块都必须缩进一个额外的块间距,并且您必须知道变量HELLO在a中的声明位置。
如果您不想通过插入try:except:逻辑来修改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,并解决循环导入问题。我知道这很丑陋,所以不要告诉别人。

好的,这基本上看起来像是一个猴子补丁的情况。有一个类似的版本可以工作 - 然而,我不确定这是否是应该做的。谢谢 :) - Juan Carlos Coto
我不确定这是否是猴子补丁。它看起来像C ++中使用的模式,即“如果模块尚未定义,请在此处定义它;如果已经定义,则跳过”。但仍有可能出现无限循环的情况,其中文件在循环依赖关系中来回反弹。有时缓解这种情况的方法是声明函数存在于与函数定义分离的文件中(C++头文件.h和代码.cpp文件)。 - Chris Dutrow

2

循环模块依赖通常是一种代码异味。

这表明代码的某部分应该进行重构,以使其成为两个模块之外的外部模块。


同意。然而,我很好奇如果已确定重构是避免它的理想选择时,它将如何解决。 - Juan Carlos Coto

2
如果我理解你的用例正确,那么“logging”访问“configuration”以获取配置数据。然而,“configuration”有一些函数,在调用时需要在“configuration”中导入来自“logging”的内容。
如果是这种情况(即,在调用函数之前,“configuration”实际上并不需要“logging”),答案很简单:在“configuration”中,将所有来自“logging”的导入放置在文件底部,在所有类、函数和常量定义之后。
Python从上到下读取内容:当它在“configuration”中遇到一个“import”语句时,它会运行它,但此时,“configuration”已经存在作为一个模块可以被导入,即使它还没有完全初始化:它只有在“import”语句运行之前声明的属性。
我同意其他人的看法,循环导入通常是代码异味。

有趣:)。我会去看看这个。是的,我也同意重构的想法,但了解可能的替代方案也很有趣...有时候重构可能不值得或可能会使代码更难理解。+1 - Juan Carlos Coto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接