【问题标题】:yaml use key or parent key as valueyaml 使用键或父键作为值
【发布时间】:2016-01-09 00:38:57
【问题描述】:

我刚刚开始使用 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

【问题讨论】:

  • 这可能是一个剪切和粘贴错误,但 Unicode 字符 2026,如您的源代码中一样,会给您一个 ScannerError。您可能指的是... 而不是。但一般来说,这些文档开始和结束标记对于单个文档文件不是必需的。
  • 感谢@Anthon,我实际上并没有使用它们。为了清楚起见,我添加了它们(以区分代码和结果)

标签: yaml pyyaml


【解决方案1】:

当你构造一个元素时你没有太多的上下文,所以你不会找到你的键,当然也不是父键来填充值,而不是在调用堆栈中挖掘上下文( loader 知道 foobarbaz,但无法确定哪个是对应的密钥或 parent_key)。

我建议你做的是创建一个特殊的节点,你用 key_construct 返回,然后在 YAML 加载后,遍历你的 yaml.load() 返回的结构。除非您有其他 ! 对象,这使得遍历结果组合比纯序列/列表和映射/字典的组合更难¹:

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 是一个功能超集,应该同样工作。

【讨论】:

  • 谢谢@Anthon。如果我需要走结构,那么其他一切不是多余的吗? (add_constructor and Expand) 我可以简单地删除感叹号并检查“。”的字符串值。或“..”我确实希望有另一种方式。一种不涉及再次遍历结构的方式,但我想我只需要忍受这几毫秒的浪费:)
  • @Ofir 如果您不打算使用任何应为... 的字符串标量,这只是多余的。如果你能接受,你确实可以将整个事情简化为expande() function()。
  • 如果您想在不进行回溯的情况下执行此操作,则需要更新解析器,以便在启动/结束新映射时将当前键值的插槽添加到某个堆栈中,这样更容易部分。鉴于您还想使用锚点和别名,您需要将堆栈与锚点定义一起存储。我看了看,当然这是可能的,但“只是实现”为Stack Exchange 答案
猜你喜欢
  • 1970-01-01
  • 2020-02-04
  • 2021-08-20
  • 1970-01-01
  • 2019-02-23
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
  • 1970-01-01
相关资源
最近更新 更多