9

我有一个 YAML 配置,如下所示:

config:
 - id: foo
 - name: bar
content:
 - run: xxx
 - remove: yyy

我正在使用 Python YAML 模块来加载它,但我想以更好的方式访问它,例如:

stream = open(filename)
config = load(stream, Loader=Loader)
print(config['content'])

我想要的是能够做到:print(config.content).

4

2 回答 2

11

您可以使用以下类将对象表示法与字典一起使用,如答案中所述:

class DictAsMember(dict):
    def __getattr__(self, name):
        value = self[name]
        if isinstance(value, dict):
            value = DictAsMember(value)
        return value

这个类在行动:

>>> my_dict = DictAsMember(one=1, two=2)
>>> my_dict
{'two': 2, 'one': 1}
>>> my_dict.two
2

编辑这递归地与子词典一起工作,例如:

>>> my_dict = DictAsMember(one=1, two=2, subdict=dict(three=3, four=4))
>>> my_dict.one
1
>>> my_dict.subdict
{'four': 4, 'three': 3}
>>> my_dict.subdict.four
4
于 2012-06-15T10:52:41.223 回答
4

The easiest way to do this is probably to overwrite the YAML constructor for tag:yaml.org,2002:map so it returns a custom dictionary class instead of an ordinary dictionary.

import yaml

class AttrDict(object):
    def __init__(self, attr):
        self._attr = attr
    def __getattr__(self, attr):
        try:
            return self._attr[attr]
        except KeyError:
            raise AttributeError

def construct_map(self, node):
    # WARNING: This is copy/pasted without understanding!
    d = {}
    yield AttrDict(d)
    d.update(self.construct_mapping(node))

# WARNING: We are monkey patching PyYAML, and this will affect other clients!    
yaml.add_constructor('tag:yaml.org,2002:map', construct_map)

YAML = """
config:
  - id: foo
  - name: bar
content:
  - run: xxx
  - remove: yyy
"""

obj = yaml.load(YAML)

print(obj.config[0].id) # prints foo

Note that this will break everything else in the process that uses YAML, if it expects everything to work the normal Python way. You can use a custom loader, but I personally find the PyYAML documentation a bit labyrinthine, and it seems that side effects are global and contagious as a rule rather than an exception.

You have been warned.

As an alternative, if your schema is relatively static you could write your own classes and deserialize to those (e.g., class Config with id and name properties). It probably wouldn't be worth the cost of the extra code, however.

于 2012-06-15T11:19:12.203 回答