灵活处理 YAML 重复键条目

Flexibility in handling YAML duplicate key entries

我正在使用 YAML 文件来允许用户为我正在开发的 python 程序配置串行工作流程:

step1:
    method1:
        param_x: 44
    method2:
        param_y: 14
        param_t: string   
    method1:
        param_x: 22
step2:
    method2:
        param_z: 7
    method1:
        param_x: 44
step3:
    method3:
        param_a: string

然后在 python 中对其进行解析并存储为字典。现在,我知道 YAML 中的重复键和 python 字典是不允许的( 为什么 ,顺便说一句?),但 YAML 似乎非常适合我的情况,因为它清晰且极简主义。

我尝试遵循此问题 () 中建议的方法。但是,在我的例子中,有时它们是重复的,有时不是,并且使用建议的 construct_yaml_map,这将创建一个 dict 或一个列表,这不是我想要的。根据节点深度,我希望能够将 第二级 (method1、method2、...)上的键和值发送到 python 字典中的列表,请避免重复问题。

虽然解析 ruamel.yaml 除了在 文档的根级别(除其他外,为了允许 根级文字标量不缩进)。添加这样一个深度的概念会很困难, 因为你必须处理别名和可能的递归事件 的数据,我也不确定这通常意味着什么(尽管对于您的示例来说足够清楚)。

在 ruamel.yaml 的默认往返加载程序中创建映射的方法相当长。 但是如果你打算把映射值混在一起,你不应该期望 能够把它们倒回去。更不用说保留评论,别名等。以下假设 您将使用更简单的安全加载程序,具有别名 and/or 合并密钥。

import sys
import ruamel.yaml

yaml_str = """\
step1:
    method1:
        param_x: 44
    method2:
        param_y: 14
        param_t: string   
    method1:
        param_x: 22
step2:
    method2:
        param_z: 7
    method1:
        param_x: 44
step3:
    method3:
        param_a: string
"""

from ruamel.yaml.nodes import *
from ruamel.yaml.compat import Hashable, PY2


class MyConstructor(ruamel.yaml.constructor.SafeConstructor):
    def construct_mapping(self, node, deep=False):
        if not isinstance(node, MappingNode):
            raise ConstructorError(
                None, None, 'expected a mapping node, but found %s' % node.id, node.start_mark
            )
        total_mapping = self.yaml_base_dict_type()
        if getattr(node, 'merge', None) is not None:
            todo = [(node.merge, False), (node.value, False)]
        else:
            todo = [(node.value, True)]
        for values, check in todo:
            mapping = self.yaml_base_dict_type()  # type: Dict[Any, Any]
            for key_node, value_node in values:
                # keys can be list -> deep
                key = self.construct_object(key_node, deep=True)
                # lists are not hashable, but tuples are
                if not isinstance(key, Hashable):
                    if isinstance(key, list):
                        key = tuple(key)
                if PY2:
                    try:
                        hash(key)
                    except TypeError as exc:
                        raise ConstructorError(
                            'while constructing a mapping',
                            node.start_mark,
                            'found unacceptable key (%s)' % exc,
                            key_node.start_mark,
                        )
                else:
                    if not isinstance(key, Hashable):
                        raise ConstructorError(
                            'while constructing a mapping',
                            node.start_mark,
                            'found unhashable key',
                            key_node.start_mark,
                        )
                value = self.construct_object(value_node, deep=deep)
                if key in mapping:
                    if not isinstance(mapping[key], list):
                        mapping[key] = [mapping[key]]
                    mapping[key].append(value)
                else:
                    mapping[key] = value
            total_mapping.update(mapping)
        return total_mapping


yaml = ruamel.yaml.YAML(typ='safe')
yaml.Constructor = MyConstructor
data = yaml.load(yaml_str)
for k1 in data: 
    # might need to guard this with a try-except for non-dictionary first-level values
    for k2 in data[k1]:
         if not isinstance(data[k1][k2], list):   # make every second level value a list
             data[k1][k2] = [data[k1][k2]]
print(data['step1'])

给出:

{'method1': [{'param_x': 44}, {'param_x': 22}], 'method2': [{'param_y': 14, 'param_t': 'string'}]}