问题描述
我刚刚开始使用 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
知道 foo
、bar
和 baz
>,但不能用于确定哪个是相应的键或 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'}}
¹
¹
这篇关于yaml 使用键或父键作为值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!