灵活处理 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'}]}
我正在使用 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'}]}