如何正确处理 Python 中的循环模块依赖关系?

Posted

技术标签:

【中文标题】如何正确处理 Python 中的循环模块依赖关系?【英文标题】:How to properly handle a circular module dependency in Python? 【发布时间】:2013-12-21 08:31:03 【问题描述】:

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

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

问题:

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

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

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

    猴子补丁模块。这听起来还不错:将logging 模块动态加载到configuration初始导入之后,并且在实际使用它的任何功能之前。不过,这意味着定义全局的、每个模块的变量。

    依赖注入。我已经阅读并遇到了依赖注入替代方案(特别是在 Java 企业领域),它们消除了一些令人头疼的问题;但是,它们可能过于复杂而无法使用和管理,这是我们想要避免的。不过,我不知道全景图在 Python 中是如何描述的。

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

非常感谢!

【问题讨论】:

将共享位放入其自己的(第三个)文件中,并将其导入到其他两个文件中 将导入放在if True: 语句中是否可行? @JoranBeasley 这是最受欢迎的答案。很有可能这就是我们最终要做的事情。谢谢。 @jcfollower 是的,可能。那是一种猴子补丁,不是吗?谢谢! 【参考方案1】:

如前所述,可能需要进行一些重构。根据名称,如果日志模块使用配置可能没问题,当考虑配置中应该有什么时,考虑配置参数,然后出现一个问题,为什么要配置日志?

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

没有内在知识,仅使用常识,“配置”模块应该是简单的东西,不需要太多处理,它应该是导入树中的一个叶子。

希望对你有帮助!

【讨论】:

谢谢,我同意。但是,我有点想以“假设这是不可能的”的方式问这个问题,以确定可能的替代方案。 @JuanCarlosCoto:从技术上讲,这绝不是“不可能的”。唯一“不可能”的情况是您不想这样做,或者您的老板不想这样做 @slebetman 是的——但有时它是关于权衡的。如果有一个简单的解决方案可以使代码更具可读性,或者如果有一种方法可以提高可维护性,那么实现它可能会很好。无论如何,这个问题的主要目的是获得消息灵通、经验丰富的开发人员的意见,他们很可能会说一些很棒的事情。例如,我认为有趣的是,还没有人提出依赖注入解决方案——这是我没想到的。不过,感谢您的意见;我完全同意“永远不可能”的说法。【参考方案2】:

这对你有用吗?

# 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 中的 import 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,并覆盖循环导入。

我知道这很恶心,所以不要告诉任何人。

【讨论】:

好的,所以这基本上看起来像是猴子修补的情况。有一个类似的版本可以工作 - 但是,我不确定这是应该做什么。谢谢:) 我不确定这是猴子补丁。它看起来像 C++ 中使用的模式,即“如果尚未定义模块,请在此处定义,如果有,则跳过”。仍然存在会产生无限循环的情况,尽管它会在循环依赖中的文件之间来回弹跳。有时缓解这种情况的方法是声明函数存在于与函数定义(C++ 头文件、.h 和代码、.cpp 文件)分开的文件中【参考方案3】:

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

它表示应该重构部分代码,使其在两个模块之外。

【讨论】:

同意。但是,我很好奇如何解决它,如果确定重构以避免它不是理想的做法。【参考方案4】:

如果我没看错你的用例,logging 会访问configuration 以获取配置数据。但是,configuration 有一些函数,在调用这些函数时,需要将来自 logging 的内容导入到 configuration 中。

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

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

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

【讨论】:

有趣:)。我会检查一下。是的,我也同意重构的想法,但是对可能的替代方案有一个很好的概述是很有趣的……有时进行重构可能不值得,或者可能会使其难以理解。 +1

以上是关于如何正确处理 Python 中的循环模块依赖关系?的主要内容,如果未能解决你的问题,请参考以下文章

ruby:如何正确地要求(避免循环依赖)

如何正确地实现Java模块与inter-module Maven构建测试依赖关系

处理 es6 模块中的依赖关系,用于节点和浏览器

如何使用 python 的 asyncio 模块正确创建和运行并发任务?

通过将同一个库链接两次来解决循环依赖关系?

商店之间的循环模块依赖关系