【问题标题】:Gracefully loading YAML config with default methods使用默认方法优雅地加载 YAML 配置
【发布时间】:2015-10-05 08:20:35
【问题描述】:

我正在尝试寻找一种优雅的方式将存储在 YAML 文件中的值加载到我的 __init__ 函数中,用于多个类。如果我有一个包含以下内容的 YAML 文件:

#YAML
an_object:
  var_a: 1
  var_b: 2

使用以下模块:

class SomeClass(object):
    def __init__(self, var_a=10, var_b=20, var_c=30):
        self.var_a = var_a
        self.var_b = var_b
        self.var_c = var_c

我想使用 YAML 文件中的值(如果存在),否则使用 __init__ 方法默认值(如果不存在)。我还希望它可以扩展到许多变量 (>10),所以我不想尝试/排除每个参数。

我的第一反应是:

import yaml 

class SomeClass(object):
    def __init__(self, var_a=10, var_b=20, var_c=30):
        self.var_a = var_a
        self.var_b = var_b
        self.var_c = var_c

        with open('config.yaml','r') as stream:
            cfg = yaml.load(stream)['an_object']

            for key, value in cfg.iteritems():
                if hasattr(self, key):
                    exec("self.{} = {}".format(key, value))

但我宁愿不必使用exec,也不想分配变量两次(这有点令人困惑)。有一个更好的方法吗?它还会覆盖非默认函数值,我也不想这样做。

【问题讨论】:

  • 你不需要使用exec,你可以使用setattr(self, key, value)

标签: python configuration yaml pyyaml


【解决方案1】:

你应该使用setattr():

import yaml 

class SomeClass(object):
    def __init__(self, var_a=10, var_b=20, var_c=30):
        self.var_a = var_a
        self.var_b = var_b
        self.var_c = var_c

        with open('config.yaml','r') as stream:
            cfg = yaml.load(stream)['an_object']

            for key, value in cfg.iteritems():
                if hasattr(self, key):
                    setattr(self, key, value)

请注意,这会将值设置为 YAML 确定文件中的值的类型。因此,您必须注意这是否与您期望的对象相匹配,或者进行显式转换。

还要注意,这是非常低效的,因为对于此类的每个实例,YAML 文件都会被打开和读取。如果您为不同的类创建了许多对象,则应该将所有这些类作为一个 Loader 单例类的子类,或者在类变量中从 yaml 读取值,然后读取一次值,然后您可以从那里的值(而不是从文件中)在每个实例化对象中执行此更新。

如果值存在于 YAML 中,为了防止分配默认值,您必须首先测试这些值是否存在,如果不存在则取默认值:

import ruamel.yaml as yaml

class Loader(object):
    _val = None

    @property
    def yaml_values(self):
        if Loader._val is None:
            with open('config.yaml','r') as stream:
                Loader._val = yaml.load(stream)
        return Loader._val

    def get_val(self, object_id, key, default):
        try:
            return self.yaml_values[object_id][key]
        except KeyError:
            return default


class SomeClass(Loader):
    obj_id = 'an_object'

    def __init__(self, var_a=10, var_b=20, var_c=30):
        for key in ['var_a', 'var_b', 'var_c']:
            setattr(self, key,
                    self.get_val(SomeClass.obj_id, key, locals()[key]))

    def __repr__(self):
        return 'SomeClass(var_a={}, var_b={}, var_c={})'.format(
            self.var_a, self.var_b, self.var_c)


sc = SomeClass()
print sc

将打印:

SomeClass(var_a=1, var_b=2, var_c=30)

【讨论】:

    【解决方案2】:

    与其将 YAML 放在 __init__ 中,我意识到在创建对象时使用配置会更好:

    import yaml 
    
    class SomeClass(object):
        def __init__(self, var_a=10, var_b=20, var_c=30):
            self.var_a = var_a
            self.var_b = var_b
            self.var_c = var_c
    
    if __name__ == 'main':
        with open('config.yaml','r') as stream:
            cfg = yaml.load(stream)
        sc = SomeClass(**cfg)
    

    这会保留默认值,唯一的主要冲突是分配多个关键字,但这是一个简单的解决方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-27
      • 2013-06-25
      • 2014-11-04
      • 1970-01-01
      • 1970-01-01
      • 2019-10-24
      相关资源
      最近更新 更多