PyYAML:加载和转储 yaml 文件并保留标签 (!CustomTag)

2023-11-26

我想创建一个 YAML 过滤器来读取 YAML 文件、处理它并随后转储它。

它必须解析任何别名(开箱即用):

>>> yaml.dump(yaml.load("""
Foo: &bar
  name: bar
Foo2:
  <<: *bar
"""))

'Foo: {name: bar}\nFoo2: {name: bar}\n'

但也应preserve任何一种!CustomTag: foo表达式,如:

>>> yaml.dump(yaml.load("Name: !Foo bar "))
yaml.constructor.ConstructorError: could not determine a constructor for the tag '!Foo' in "<unicode string>", line 1, column 7:
Name: !Foo bar
      ^

I read pyYAML 上的“!”错误在一个字符串中这接近我需要的,除了它将自定义标签解析并输出为带引号的字符串,因此它不再是一个标签:

>>> def default_ctor(loader, tag_suffix, node):
...   return tag_suffix + ' ' + node.value

>>> yaml.add_multi_constructor('', default_ctor)
>>> yaml.dump(yaml.load("Name: !Foo bar "), default_flow_style=False)
"Name: '!Foo bar'\n"

我想没有太多的缺失,但是什么呢?如何加载包含任何标签的文件并随后转储它们?


Since default_ctor()返回一个字符串(它只是标签和标量的串联),这就是要转储的内容。因为标签开头为!将该字符串转储为标量将为您提供引号。

如果你想一般地保留标签和值,你需要将它们存储在特殊类型(而不是“普通”Python 字符串)中,并为该类型提供一个表示器(即转储例程):

import sys
import yaml

yaml_str = """\
Name: !Foo bar
Alt: !Bar foo
"""


class GenericScalar:
    def __init__(self, value, tag, style=None):
        self._value = value
        self._tag = tag
        self._style = style

    @staticmethod
    def to_yaml(dumper, data):
        # data is a GenericScalar
        return dumper.represent_scalar(data._tag, data._value, style=data._style)


def default_constructor(loader, tag_suffix, node):
    if isinstance(node, yaml.ScalarNode):
        return GenericScalar(node.value, tag_suffix, style=node.style)
    else:
        raise NotImplementedError('Node: ' + str(type(node)))


yaml.add_multi_constructor('', default_constructor, Loader=yaml.SafeLoader)

yaml.add_representer(GenericScalar, GenericScalar.to_yaml, Dumper=yaml.SafeDumper)

data = yaml.safe_load(yaml_str)
yaml.safe_dump(data, sys.stdout, default_flow_style=False, allow_unicode=True)

这给出:

Alt: !Bar 'foo'
Name: !Foo 'bar'

Notes:

  • 使用 PyYAML 是不安全的load(). 不要使用它,这是没有必要的(如我的代码所示)。更糟糕的是,PyYAML 没有反馈任何危险。
  • PyYAML 会转储所有带有引号的标量,即使您像我一样保留节点样式(或强制为空字符串)。为了防止这种情况发生,您必须深入研究节点的序列化。我一直在我的 ruamel.yaml 包中解决这个问题,因为引号通常是不必要的。
  • 您的锚点和别名未得到解析。只是 PyYAML 不够聪明,除了扩展合并键在加载时。如果您的 YAML 中有正常的自引用,您将在转储的 YAML 中获得锚点和别名。
  • 如果标签后面的节点不是标量(即映射或序列),上面的代码会很好地引发错误。也可以一般地加载/转储它们。只需添加一些类型并扩展default_constructor和一些elif isinstance(node, yaml.MappingNode) and elif isinstance(node, yaml.SequenceNode)。我会让它们创建不同的类型(其行为类似于字典或列表),如果您走这条路,您应该意识到构建这些类型需要分两步进行(yield构造的对象,然后获取子节点值并填充对象),否则不能使用自引用结构(即节点内的别名)。
  • PyYAML 不保留映射中元素的顺序
  • 你可以有一个标签!CustomTag:以冒号结尾,但我发现它读起来不太友好!CustomTag: foo,因为它看起来非常像块样式映射中的键值对。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PyYAML:加载和转储 yaml 文件并保留标签 (!CustomTag) 的相关文章

随机推荐