PyYAML 使用重复键加载 YAML 1.1

PyYAML loading YAML 1.1 with duplicate keys

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

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

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

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

阅读 YAML 文档第 10.2 节后,我预计会收到警告,并且 aaa 的值为 9:

It is an error for two equal keys to appear in the same mapping node. In such a case the YAML processor may continue, ignoring the second key: value pair and issuing an appropriate warning.

但我没有收到任何警告,并且值为 12。

这是一个错误吗?有没有办法让 PyYAML 获得 select 键的第一个值?

我查看了一些其他语言的库,以便在进一步处理之前清理它,但这些库要么抛出错误,要么继续使用第二个值。

有很多文件,通常重复项嵌套得更深。它们的键之间可以有复杂的结构,并且重复的键对于它们出现的映射也不是唯一的,这是有效的。使用 awk 修复此问题将无法处理这些文件。而且太多了,无法手动修复。

我会说这是 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

然后使用 Constructor 创建一个 Loader

使用起来可能更简单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 将始终这样做。