yaml 使用键或父键作为值

yaml use key or parent key as value

我刚开始使用 YAML(通过 pyyaml),我想知道是否有任何方法可以声明键的值是键名本身或父键。 例如

---
foo: &FOO
  bar: !.
  baz: !..

foo2:
  <<: *FOO
…

{‘foo’: {‘bar’: ‘bar’, ‘baz’: ’foo’}, ‘foo2’:{‘bar’:’bar’, ‘baz’:’foo2’}}

(分别注意 bar 和 baz 上的点和双点——它们只是用于获取键名和父键名的占位符)

我试过使用 add_constructor:

def key_construct(loader, node):
    # return the key here
    pass

yaml.add_constructor('!.', key_construct)

但是 Node 没有保存密钥(或对父项的引用),我找不到获取它们的方法。

编辑:

所以,这是我的真实用例和基于 Anthon 响应的解决方案: 我有一个记录器配置文件(在 yaml 中),我想在那里重用一些定义:

handlers:
    base: &base_handler
        (): globals.TimedRotatingFileHandlerFactory
        name: ../
        when: midnight
        backupCount: 14

        level: DEBUG
        formatter: generic

    syslog:
        class: logging.handlers.SysLogHandler
        address: ['localhost', 514]
        facility: local5

        level: NOTSET
        formatter: syslog

    access:
        <<: *base_handler

    error:
        <<: *base_handler

loggers:
    base: &base_logger
        handlers: [../, syslog]
        qualname: ../

    access:
        <<: *base_logger

    error:
        <<: *base_logger
        handlers: [../, syslog, email]

Anthon 建议的解决方案是在处理后遍历配置字典:

def expand_yaml(val, parent=None, key=None, key1=None, key2=None):
    if isinstance(val, str):
        if val == './':
            parent[key] = key1
        elif val == '../':
            parent[key] = key2
    elif isinstance(val, dict):
        for k, v in val.items():
            expand_yaml(v, val, k, k, key1)
    elif isinstance(val, list):
        parent[key] = val[:] # support inheritance of the list (as in *base_logger)
        for index, e in enumerate(parent[key]):
            expand_yaml(e, parent[key], index, key1, key2)
    return val

您在构建元素时没有太多上下文,因此您不会找到您的键,当然也不会找到父键来填充值,而无需挖掘调用堆栈上下文(loader 知道 foobarbaz,但无法确定哪个是相应的键或 parent_key) .

我建议你做的是用 key_construct 创建一个你 return 的特殊节点,然后在 YAML 加载之后,遍历你的 yaml.load() return编辑。除非你有其他 ! 对象,这使得遍历结果组合比 sequences/lists 和 mappings/dicts 的纯组合更难 ¹:

import ruamel.yaml as yaml

yaml_str = """\
foo: &FOO
  bar: !.
  baz: !..

foo2:
  <<: *FOO
"""

class Expander:
    def __init__(self, tag):
        self.tag = tag

    def expand(self, key, parent_key):
        if self.tag == '!.':
            return key
        elif self.tag == '!..':
            return parent_key
        raise NotImplementedError

    def __repr__(self):
        return "E({})".format(self.tag)

def expand(d, key=None, parent_key=None):
    if isinstance(d, list):
        for elem in d:
            expand(elem, key=key, parent_key=parent_key)
    elif isinstance(d, dict):
        for k in d:
            v = d[k]
            if isinstance(v, Expander):
                d[k] = v.expand(k, parent_key)
            expand(d[k], key, parent_key=k)
    return d


def key_construct(loader, node):
    return Expander(node.tag)

yaml.add_constructor('!.', key_construct)
yaml.add_constructor('!..', key_construct)


data = yaml.load(yaml_str) 
print(data)
print(expand(data))

给你:

{'foo': {'bar': E(!.), 'baz': E(!..)}, 'foo2': {'bar': E(!.), 'baz': E(!..)}}
{'foo': {'bar': 'bar', 'baz': 'foo'}, 'foo2': {'bar': 'bar', 'baz': 'foo2'}}

¹ 这是使用 ruamel.yaml 完成的,我是作者。 PyYAML,其中 ruamel.yaml 是一个功能超集,应该工作相同。