【问题标题】:Dynamically broadcast configuration changes in python twistedpython twisted中动态广播配置更改
【发布时间】:2013-01-23 16:07:54
【问题描述】:

我即将重构一个建立在twisted 之上的python 项目的代码。到目前为止,我一直在使用一个简单的 settings.py 模块来存储常量和字典,例如:

#settings.py
MY_CONSTANT='whatever'
A_SLIGHTLY_COMPLEX_CONF= {'param_a':'a', 'param_b':b}

大量模块导入settings.py 来完成它们的工作。

我想要重构项目的原因是因为我需要动态更改/添加配置参数。我即将采取的方法是将所有配置收集在一个单例中,并在需要时访问它的实例。

import settings.MyBloatedConfig

def first_insteresting_function():
    cfg = MyBloatedConfig.get_instance()
    a_much_needed_param = cfg["a_respectable_key"]
    #do stuff

#several thousands of functions later

def gazillionth_function_in_module():
    tired_cfg = MyBloatedConfig.get_instance()
    a_frustrated_value = cfg["another_respectable_key"]
    #do other stuff

这种方法有效,但感觉不合时宜且臃肿。另一种方法是将模块中的cfg 对象外部化,如下所示:

CONFIG=MyBloatedConfig.get_instance()

def a_suspiciously_slimmer_function():
    suspicious_value = CONFIG["a_shady_parameter_key"]

不幸的是,如果我要更改另一个模块中的 MyBloatedConfig 实例条目,这将不起作用。由于我使用的是反应器模式,因此将人员存储在 本地线程 上以及使用队列是没有问题的。

为了完整起见,以下是我用来实现单例模式的实现

instances = {}
def singleton(cls):
    """ Use class as singleton. """
    global instances

    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class MyBloatedConfig(dict):
    .... 

是否有其他更 Pythonic 的方式可以在不同模块间广播配置更改?

【问题讨论】:

    标签: python configuration singleton twisted


    【解决方案1】:

    大的、全局的(通常是单例的)配置对象是一种反模式。

    无论您有settings.pyMyBloatedConfig.get_instance() 样式的单例,还是您在此处概述的任何其他方法,您基本上都在使用相同的反模式。确切的拼写无关紧要,这些都是让整个项目中的所有代码共享一个真正的全局(不同于 Python 模块级全局)的方法。

    这是一种反模式,原因有很多:

    • 它使您的代码难以进行单元测试。任何基于此全局更改其行为的代码都将需要某种黑客攻击 - 通常是猴子补丁 - 以便让您在不同配置下对它的行为进行单元测试。将此与编写为接受参数(如函数参数)并根据传递给它的值改变其行为的代码进行比较。
    • 它会降低您的代码的可重用性。由于配置是全局的,如果您想在两种不同的配置下使用依赖于该配置对象的任何代码,您将不得不跳过障碍。你的单例只能代表一种配置。因此,您必须来回交换全局状态才能获得所需的不同行为。
    • 它使您的代码更难理解。如果您查看一段使用全局配置的代码,并且想知道它是如何工作的,那么您将不得不查看配置。但是,比这更糟糕的是,如果您想更改配置,则必须查看整个代码库以查找可能会影响的任何代码。这会导致配置随着时间的推移而增长,因为您向其中添加新项目并且很少删除或修改旧项目,以免破坏某些东西(或没有时间正确追踪旧项目的所有用户)。李>

    以上问题应该会提示您解决方案是什么。如果你有一个函数需要知道某个常量的值,让它接受那个值作为参数。如果您有一个需要大量值的函数,则创建一个类,该类可以将这些值包装在一个方便的容器中,并将该类的一个实例传递给该函数。

    这个解决方案经常困扰人们的部分是他们不想花时间输入所有这些参数传递的部分。之前你的函数可能需要一个或两个(甚至零个)参数,而现在你将拥有可能需要三个或四个参数的函数。而且,如果您正在转换以settings.py 样式编写的应用程序,那么您可能会发现您的某些函数使用了全局配置中的六个或更多项,并且这些函数突然有一个很长的签名。

    我不会质疑这是一个潜在的问题,但应该主要将其视为现有代码的结构和组织的问题。以非常长的签名结束的函数依赖于之前的所有数据。你只是掩盖了这个事实。与大多数对您隐藏程序方面的编程模式一样,这是一件坏事。一旦你明确地传递了所有这些值,你就会看到你的抽象需要在哪里工作。也许这 10 个参数函数做得太多了,作为三个不同的函数会更好。或者您可能会注意到这些参数中有一半实际上是相关的,并且始终作为容器对象的一部分属于一起。或许您甚至可以将一些与操作这些参数相关的逻辑放到该容器对象上。

    【讨论】:

    • 我很清楚单例反模式,但就我而言,MyBlocatedConfig 全局对象(为简洁起见被描述为 dict)体现了我的应用程序的核心。您可以将其与reactor 对象进行比较。 reactor 是一个与twisted 密切相关的单例。您避免在函数签名中显式传递它,导入它(也许是为了避免显式重新加载模块)并使用它。我欢迎建议避免导入和使用全局内容,但不必用我的应用程序中无所不在的东西污染签名。
    • reactor 是单例被广泛认为是错误的。有解决这个错误的长期计划。 :) 但是,如果你说你的全局对象是你的应用程序的核心,那么我几乎只需要相信你。在这种情况下,我没有太多建议,祝你好运。
    • 非常有趣。也许如果你能给我指出一些关于这种长期计划的文档,我可能会受到启发来重构我的代码(也可能做出贡献:))
    • 我开始重构代码,我确实发现尽可能避免使用全局对象可以提高清晰度,可测试性甚至可扩展性,因为应用程序最终会变得更加无状态。对于在运行时完成的配置更改,代码必须以这样一种方式组织,即很少有对象负责根据配置更改来编排参数,这与让函数从全局对象中提取参数不同。这些对象被注入到配置端点,以便收到更改通知。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多