如何设计具有许多配置选项的程序?

时间:2011-09-07 05:46:44

标签: python api coding-style

假设我有一个包含大量配置选项的程序。用户可以在配置文件中指定它们。我的程序可以解析这个配置文件,但它应该如何在内部存储并传递选项?

就我而言,该软件用于执行科学模拟。大约有200个选项,其中大多数具有合理的默认值。通常,用户只需指定十几个左右。我面临的困难是如何设计我的内部代码。许多需要构造的对象依赖于许多配置选项。例如,一个对象可能需要几个路径(用于存储数据的位置),一些需要传递给对象将调用的算法的选项,以及一些由对象本身直接使用的选项。

这导致需要构造大量参数的对象。此外,由于我的代码库处于非常活跃的开发阶段,因此通过调用堆栈并将新的配置选项一直传递到需要的地方是一件非常大的痛苦。

防止这种痛苦的一种方法是拥有一个可以在代码中的任何地方自由使用的全局配置对象。我并不特别喜欢这种方法,因为它导致函数和类不带任何(或只有一个)参数,并且读者不清楚函数/类处理什么数据。它还可以防止代码重用,因为所有代码都依赖于巨大的配置对象。

任何人都可以就如何构建这样的程序给我一些建议吗?

以下是我对配置选项传递样式的一个示例:

class A:
    def __init__(self, opt_a, opt_b,  ..., opt_z):
        self.opt_a = opt_a
        self.opt_b = opt_b
        ...
        self.opt_z = opt_z

    def foo(self, arg):
        algo(arg, opt_a, opt_e)

以下是全局配置样式的示例:

class A:
    def __init__(self, config):
        self.config = config

    def foo(self, arg):
        algo(arg, config)

示例在Python中,但我的问题代表任何类似的编程语言。

4 个答案:

答案 0 :(得分:5)

matplotlib是一个包含许多配置选项的大型包。它使用rcParams模块来管理所有默认参数。 rcParams保存dict中的所有默认参数。

每个函数都会从关键字argurments中获取选项:

例如:

def f(x,y,opt_a=None, opt_b=None):
    if opt_a is None: opt_a = rcParams['group1.opt_a']

答案 1 :(得分:3)

一些设计模式将有所帮助

  • 原型
  • 工厂和抽象工厂

将这两种模式与配置对象一起使用。然后,每个方法将获取配置对象并使用它所需的内容。还要考虑应用逻辑分组来配置参数,并考虑减少输入数量的方法。

伪造的代码

// Consider we  can run three different kinds of Simulations. sim1, sim2, sim3

ConfigFactory configFactory = new ConfigFactory("/path/to/option/file");
....
Simulation1 sim1;
Simulation2 sim2;
Simulation3 sim3;

sim1.run( configFactory.ConfigForSim1() );
sim2.run( configFactory.ConfigForSim2() );
sim3.run( configFactory.ConfigForSim3() );

在每个工厂方法的内部,它可以从原型对象(具有所有“理智”默认值)创建配置,并且选项文件变为与默认值不同的选项。这将与关于这些默认值以及个人(或其他程序)可能想要更改它们的明确文档配对。

**编辑:** 还要考虑工厂返回的每个配置都是整个配置的子集。

答案 2 :(得分:2)

传递配置解析类,或者编写一个包装它的类,并智能地提取所请求的选项。

Python的标准库configparser使用映射协议公开INI样式配置文件的部分和选项,因此您可以直接从中检索选项,就像它是字典一样。

myconf = configparser.ConfigParser()
myconf.read('myconf.ini')
what_to_do = myconf['section']['option']

如果您明确要使用属性表示法提供选项,请创建一个覆盖__getattr__的类:

class MyConf:
    def __init__(self, path):
        self._parser = configparser.ConfigParser()
        self._parser.read('myconf.ini')
    def __getattr__(self, option):
        return self._parser[{'what_to_do': 'section'}[option]][option]

myconf = MyConf()
what_to_do = myconf.what_to_do

答案 3 :(得分:0)

让模块将params加载到其命名空间,然后导入它并在任何地方使用。
另请参阅相关问题here