【问题标题】:PyYAML loading YAML 1.1 with duplicate keysPyYAML 使用重复键加载 YAML 1.1
【发布时间】:2019-05-24 00:37:14
【问题描述】:

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

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

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

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

通过阅读 YAML 文档第 10.2 节,我预计会收到警告,aaa 的值将是 9:

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

但我没有收到警告,值为 12。

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

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

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

【问题讨论】:

    标签: python-3.x yaml pyyaml


    【解决方案1】:

    我会说这是 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 将始终这样做。

    【讨论】:

    • 我见过 ruamel,yaml 但它是一个 1.2 解析器,我认为它不能正确处理八进制等。我应该提交错误报告
    • 我不会为错误报告或 PR 而烦恼,下次 PyYAML 更改错误和存储库的托管位置时它“丢失”的可能性大于问题将得到解决。
    猜你喜欢
    • 2012-03-01
    • 1970-01-01
    • 2015-10-23
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    • 2012-11-11
    • 1970-01-01
    • 2011-10-12
    相关资源
    最近更新 更多