PyYAML 只会默默地覆盖第一个条目,ruamel.yaml ¹ 将给出一个DuplicateKeyFutureWarning
if 与旧 API 一起使用,并引发一个DuplicateKeyError
新 API。
如果您不想Constructor
为所有类型创建一个完整的,覆盖映射构造函数SafeConstructor
应该可以完成这项工作:
import sys
from ruamel.yaml import YAML
from ruamel.yaml.constructor import SafeConstructor
yaml_str = """\
build:
step: 'step1'
build:
step: 'step2'
"""
def construct_yaml_map(self, node):
# test if there are duplicate node keys
data = []
yield data
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=True)
val = self.construct_object(value_node, deep=True)
data.append((key, val))
SafeConstructor.add_constructor(u'tag:yaml.org,2002:map', construct_yaml_map)
yaml = YAML(typ='safe')
data = yaml.load(yaml_str)
print(data)
这使:
[('build', [('step', 'step1')]), ('build', [('step', 'step2')])]
但是,似乎没有必要step: 'step1'
列入清单。以下将仅在存在重复项时创建列表(如有必要,可以通过缓存 的结果进行优化self.construct_object(key_node, deep=True)
):
def construct_yaml_map(self, node):
# test if there are duplicate node keys
keys = set()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=True)
if key in keys:
break
keys.add(key)
else:
data = {} # type: Dict[Any, Any]
yield data
value = self.construct_mapping(node)
data.update(value)
return
data = []
yield data
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=True)
val = self.construct_object(value_node, deep=True)
data.append((key, val))
这使:
[('build', {'step': 'step1'}), ('build', {'step': 'step2'})]
几点:
- 可能不用说,这不适用于YAML 合并键(
<<: *xyz
)
- 如果您需要 ruamel.yaml 的往返功能 (
yaml = YAML()
),那将需要更复杂的construct_yaml_map
.
如果要转储输出,则应为此实例化一个新YAML()
实例,而不是重新使用用于加载的“已修补”实例(它可能有效,这只是为了确定):
yaml_out = YAML(typ='safe')
yaml_out.dump(data, sys.stdout)
这给出了(第一个construct_yaml_map
):
- - build
- - [step, step1]
- - build
- - [step, step2]
在 PyYAML 和 ruamel.yaml 中不起作用的是yaml.load('file.yml')
. 如果您不想open()
自己归档文件,可以执行以下操作:
from pathlib import Path # or: from ruamel.std.pathlib import Path
yaml = YAML(typ='safe')
yaml.load(Path('file.yml')
¹免责声明:我是该软件包的作者。