PyYAML使用重复键加载YAML 1.1

时间:2018-12-25 07:43:58

标签: python-3.x yaml pyyaml

我正在尝试使用PyYAML加载YAML 1.1文件(它们没有这样标记,但它们具有八进制整数值 0123 而不是 0o123 )。

我不知道这些文件是如何生成的,但是问题之一是其中一些文件具有重复的密钥,例如:

xxx:
   aaa: 011
   bbb: 012
   ccc: 013
   aaa: 014

我正在使用yaml.safe_load()加载这些文件。

通过阅读YAML文档的10.2节,我希望得到一个警告,并且aaa的值将为9:

  

两个相等的键出现在同一映射节点中是错误的。        在这种情况下,YAML处理器可能会继续运行,而忽略第二个        关键:值对并发出适当的警告。

但是我没有得到警告,该值为12。

这是一个错误吗?有没有办法让PyYAML为键选择第一个值?

我查看了一些其他语言的库,以在进一步处理之前对其进行清理,但是这些库确实抛出了错误,或者继续使用第二个值。

文件很多,通常重复项嵌套得更深。它们在键之间可能具有复杂的结构,并且重复的键也不是它们所在的映射唯一的,这是有效的。使用 awk 修复此问题将无法使用这些文件。还有太多的东西无法手工修复。

1 个答案:

答案 0 :(得分:0)

我会说这是PyYAML中的错误。令人反感的代码为here

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)
    mapping = {}
    for key_node, value_node in node.value:
        key = self.construct_object(key_node, deep=deep)
        if not isinstance(key, collections.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)
        mapping[key] = value
    return mapping

很明显,不检查密钥是否存在。您必须将Constructor子类化,使其中一个construct_mapping()带有随附的支票:

        if key in mapping:
             warnings.warn(somewarning)
        else:
            mapping[key] = value

然后使用该Loader创建一个Constructor

使用ruamel.yaml可能更简单(免责声明:我是作者 那个包裹)。假设您禁用了DuplicateKeyError,它会正确加载此文件, 并将YAML 1.1明确设置为输入格式:

import sys
import ruamel.yaml

yaml_file = Path('xx.yaml')

yaml = ruamel.yaml.YAML()
yaml.version = (1, 1)
yaml.indent(mapping=3, sequence=2, offset=0)
yaml.allow_duplicate_keys = True
data = yaml.load(yaml_file)
assert data['xxx']['aaa'] == 9
yaml_out = ruamel.yaml.YAML()
yaml_out.dump(data, sys.stdout)

这给出了:

xxx:
  aaa: 9
  bbb: 10
  ccc: 11

您的八进制将转换为小数(通常该信息是 保留,但在加载旧版YAML 1.1时不保留)。 PyYAML会一直这样做。