简体   繁体   English

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

[英]How to properly handle a circular module dependency in Python?

Trying to find a good and proper pattern to handle a circular module dependency in Python.试图找到一个好的和合适的模式来处理 Python 中的循环模块依赖。 Usually, the solution is to remove it (through refactoring);通常,解决方法是将其移除(通过重构); however, in this particular case we would really like to have the functionality that requires the circular import.然而,在这种特殊情况下,我们真的希望拥有需要循环导入的功能。

EDIT : According to answers below, the usual angle of attack for this kind of issue would be a refactor.编辑:根据下面的答案,此类问题的常见攻击角度是重构。 However, for the sake of this question, assume that is not an option (for whatever reason).但是,对于这个问题,假设这不是一个选项(无论出于何种原因)。

The problem:问题:

The logging module requires the configuration module for some of its configuration data. logging模块的某些配置数据需要configuration模块。 However, for some of the configuration functions I would really like to use the custom logging functions that are defined in the logging module.但是,对于某些configuration功能,我真的很想使用在logging模块中定义的自定义日志功能。 Obviously, importing the logging module in configuration raises an error.显然,在configuration导入logging模块会引发错误。

The possible solutions we can think of:我们可以想到的可能解决方案:

  1. Don't do it .不要这样做 As I said before, this is not a good option, unless all other possibilities are ugly and bad.正如我之前所说,这不是一个好的选择,除非所有其他可能性都很丑陋和糟糕。

  2. Monkey-patch the module .猴子补丁模块 This doesn't sound too bad: load the logging module dynamically into configuration after the initial import, and before any of its functions are actually used.这听起来并不太坏:加载logging动态模块插入configuration初始导入,和之前的任何职能被实际使用。 This implies defining global, per-module variables, though.不过,这意味着定义全局的、每个模块的变量。

  3. Dependency injection .依赖注入 I've read and run into dependency injection alternatives (particularly in the Java Enterprise space) and they remove some of this headache;我已经阅读并遇到了依赖注入替代方案(特别是在 Java Enterprise 领域),它们消除了一些令人头疼的问题; however, they may be too complicated to use and manage, which is something we'd like to avoid.但是,它们的使用和管理可能过于复杂,这是我们希望避免的。 I'm not aware of how the panorama is about this in Python, though.不过,我不知道 Python 中的全景图如何。

What is a good way to enable this functionality?启用此功能的好方法是什么?

Thanks very much!非常感谢!

As already said, there's probably some refactoring needed.如前所述,可能需要进行一些重构。 According to the names, it might be ok if a logging modules uses configuration, when thinking about what things should be in configuration one think about configuration parameters, then a question arises, why is that configuration logging at all?从名字上看,如果一个日志模块使用配置可能是可以的,当考虑配置中应该有什么东西时,就会想到配置参数,那么问题就来了,为什么要配置日志?

Chances are that the parts of the code under configuration that uses logging does not belong to the configuration module: seems like it is doing some kind of processing and logging either results or errors.使用日志记录的配置下的代码部分可能不属于配置模块:似乎它正在执行某种处理并记录结果或错误。

Without inner knowledge, and using only common sense, a "configuration" module should be something simple without much processing and it should be a leaf in the import tree.没有内部知识,仅使用常识,“配置”模块应该是简单的东西,没有太多处理,它应该是导入树中的叶子。

Hope it helps!希望能帮助到你!

Will this work for you?这对你有用吗?

# 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

Now I can do an import b.现在我可以做一个导入 b。 When the circular import (caused by the import b statement in a) throws an exception, it gets caught and discarded.当循环导入(由 a 中的 import b 语句引起)抛出异常时,它会被捕获并丢弃。 Of course your entire module b will have to indented one extra block spacing, and you have to have inside knowledge of where the variable HELLO is declared in a.当然,您的整个模块 b 必须缩进一个额外的块间距,并且您必须了解变量 HELLO 在 a 中声明的位置。

If you don't want to modify b.py by inserting the try:except: logic, you can move the whole b source to a new file, call it c.py, and make a simple file b.py like this:如果你不想通过插入 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))

This will import the entire namespace from c to b, and paper over the circular import as well.这会将整个命名空间从 c 导入到 b,并覆盖循环导入。

I realize this is gross, so don't tell anyone about it.我知道这很恶心,所以不要告诉任何人。

A cyclic module dependency is usually a code smell.循环模块依赖通常是一种代码味道。

It indicates that part of the code should be re-factored so that it is external to both modules.它表示应该重构部分代码,以便它在两个模块之外。

So if I'm reading your use case right, logging accesses configuration to get configuration data.因此,如果我正确阅读了您的用例,则logging访问configuration以获取配置数据。 However, configuration has some functions that, when called, require that stuff from logging be imported in configuration .但是, configuration有一些函数,当调用时,需要将logging中的内容导入configuration

If that is the case (that is, configuration doesn't really need logging until you start calling functions), the answer is simple: in configuration , place all the imports from logging at the bottom of the file, after all the class, function and constant definitions.如果是这种情况(也就是说,在开始调用函数之前, configuration并不真正需要logging ),答案很简单:在configuration ,将所有来自logging的导入放在文件的底部,毕竟是类、函数和常量定义。

Python reads things from top to bottom: when it comes across an import statement in configuration , it runs it, but at this point, configuration already exists as a module that can be imported, even if it's not fully initialized yet: it only has the attributes that were declared before the import statement was run. Python 从上到下读取内容:当它在configuration遇到import语句时,它会运行它,但此时, configuration已经作为可以导入的模块存在,即使它还没有完全初始化:它只有在import语句运行之前声明的属性。

I do agree with the others though, that circular imports are usually a code smell.不过,我确实同意其他人的看法,循环导入通常是一种代码气味。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM