1

我即将重构一个建立在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 的方式来广播不同模块的配置更改?

4

1 回答 1

2

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

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

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

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

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

这个解决方案经常困扰人们的部分是他们不想花时间输入所有这些参数传递的部分。之前你的函数可能需要一个或两个(甚至零个)参数,而现在你将拥有可能需要三个或四个参数的函数。而且,如果您正在转换以 settings.py.

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

于 2013-02-08T01:35:43.757 回答