yaml 使用键或父键作为值 [英] yaml use key or parent key as value

查看:45
本文介绍了yaml 使用键或父键作为值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I’ve just started using YAML (through pyyaml) and I was wondering if there is any way to state that the value of a key is the key name itself or the parent key. For example

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

foo2:
  <<: *FOO
…

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

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

(notice the dot and double dot on bar and baz respectively - those are just placeholders for getting the key name and parent key name)

我尝试过使用 add_constructor:

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

yaml.add_constructor('!.', key_construct)

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

but Node, doesn't hold the key (or a reference to the parent) and I couldn't find the way to get them.

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

So, here is my real use case and a solution based on Anthon's response: I have a logger configuration file (in yaml), and I wanted to reuse some definitions there:

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 建议的解决方案是在处理后遍历配置字典:

The solution, as Anthon suggested was to traverse the configuration dictionary after is was being processed:

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).

You don't have much context when you are constructing an element, so you are not going to find your key, and certainly not the parent key, to fill in the values, without digging in the call stack for the context (the loader knows about foo, bar and baz, but not in a way you can use to determine which is the corresponding key or parent_key).

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

What I suggest you do is create a special node that you return with the key_construct and then after the YAML load, walk the structure that your yaml.load() returned. Unless you have other ! objects, which make it more difficult to walk the resulting combination than a pure combination of sequences/lists and 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 是一个功能超集,应该工作相同.


¹ This was done using ruamel.yaml of which I am the author. PyYAML, of which ruamel.yaml is a functional superset, should work the same.

这篇关于yaml 使用键或父键作为值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆