如何使用显式引用转储 YAML?

Posted

技术标签:

【中文标题】如何使用显式引用转储 YAML?【英文标题】:How to dump YAML with explicit references? 【发布时间】:2017-01-24 20:05:23 【问题描述】:

递归引用在ruamel.yamlpyyaml 中效果很好:

$ ruamel.yaml.dump(ruamel.yaml.load('&A [ *A ]'))
'&id001
- *id001'

但是它(显然)不适用于普通引用:

$ ruamel.yaml.dump(ruamel.yaml.load("foo: &foo  a: 42 \nbar:  <<: *foo "))
bar: a: 42
foo: a: 42

我想显式创建一个引用:

data = 
data['foo'] = 'foo': 'a': 42
data['bar'] =  '<<': data['foo'], 'b': 43 

$ ruamel.yaml.dump(data, magic=True)
foo: &foo
    a: 42
bar: 
    <<: *foo
    b: 43

这对于生成具有大量公共键的大型数据结构的 YAML 输出非常有用

在输出上没有有争议的 re.replace 怎么可能?

其实ruamel.yaml.dump(data)的结果是

bar:
  '<<': &id001
    foo:
      a: 42
  b: 43
foo: *id001

所以我需要将'&lt;&lt;' 替换为&lt;&lt;,并可能将id001 替换为foo

【问题讨论】:

【参考方案1】:

如果你想创建类似的东西,至少在 ruamel.yaml 中,你应该使用往返模式,它也保留了合并。以下不会引发断言错误:

import ruamel.yaml

yaml_str = """\
foo: &xyz
  a: 42
bar:
  <<: *xyz
"""

data = ruamel.yaml.round_trip_load(yaml_str)
assert ruamel.yaml.round_trip_dump(data) == yaml_str

这意味着data 有足够的信息来重新创建输出中的合并。然而,在实践中,在往返模式下,永远不会发生合并。取而代之的是检索值data['foo']['bar']['a'] 意味着data['foo'] 中没有真正的键'bar',但随后会在附加的“合并映射”中查找该键。

对此没有公共接口(因此情况可能会改变),但通过分析data 并查看ruamel.yaml.comments.CommentedMap(),您会发现有一个merge_attrib(当前为字符串_yaml_merge)等等有用的是有一个方法add_yaml_merge()。后者采用 (int, CommentedMap()) 元组的列表。

baz = ruamel.yaml.comments.CommentedMap()
baz['b'] = 196
baz.yaml_set_anchor('klm')
data.insert(1, 'baz', baz)

你需要在数据的'bar'键之前插入'baz'键,否则映射会反转。在 data['bar'] 的合并中插入新结构后:

data['bar'].add_yaml_merge([(0, baz)])
ruamel.yaml.round_trip_dump(data, sys.stdout)

给出:

foo: &xyz
  a: 42
baz: &klm
  b: 196
bar:
  <<: [*xyz, *klm]

(如果你想看看add_yaml_merge 插入了什么

print(getattr(data['bar'], ruamel.yaml.comments.merge_attrib))

通话前后)

如果你想完全从头开始,你可以这样做:

data = ruamel.yaml.comments.CommentedMap([
    ('foo', ruamel.yaml.comments.CommentedMap([('a', 42)])),
    ])
data['foo'].yaml_set_anchor('xyz')
data['bar'] = bar = ruamel.yaml.comments.CommentedMap()
bar.add_yaml_merge([(0, data['foo'])])

而不是data = ruamel.yaml.round_trip_load(yaml_str)


¹ 免责声明:我是该软件包的作者。

【讨论】:

除了d = ruamel.yaml.round_trip_load(ruamel.yaml.dump(data)),还有其他方法可以将dict 导入round_trip 对象吗? @nowox 要“从头开始”,您必须从 data = ruamel.yaml.comments.CommentedMap() 而不是 data = dict() 开始(对于您要在下面创建的任何其他必须保持键有序的字典也是如此并允许附加额外的数据(参考名称、cmets 等)。 @nowox 我用完整的代码更新了答案以摆脱round_trip_load() 谢谢,但这不是很方便。我的目标是在一个巨大的现有字典上添加一个合并。执行data = ruamel.yaml.round_trip_load(yaml_str) 速度较慢,但​​更容易执行。在您的示例中,我需要递归解析我的数据结构以创建 ruamel.yaml.comments.CommentedMap 对象。

以上是关于如何使用显式引用转储 YAML?的主要内容,如果未能解决你的问题,请参考以下文章

转储到另一个文件时如何删除引号?

如何将带有前导零的数字字符串转储为 yaml-cpp 中的有效 yaml 字符串?

通过 ruamel.yaml 转储时如何在 yaml 文件中保留空值

如何防止 YAML 在没有新行的情况下转储长行

我想加载一个 YAML 文件,可能编辑数据,然后再次转储。如何保留格式?

YAML - 转储没有类型/标签的嵌套对象