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

Jan*_*Jan 4 python parsing pyyaml

我想创建一个YAML过滤器,该过滤器读取YAML文件,对其进行处理并将其转储。

它必须解析任何别名(开箱即用的效果很好):

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

'Foo: {name: bar}\nFoo2: {name: bar}\n'
Run Code Online (Sandbox Code Playgroud)

但是它还应保留任何一种!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
      ^
Run Code Online (Sandbox Code Playgroud)

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

>>> 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"
Run Code Online (Sandbox Code Playgroud)

我想这里没有很多东西,但是呢?如何加载包含任何标签的文件,然后将其转储?

Ant*_*hon 5

由于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)
Run Code Online (Sandbox Code Playgroud)

这给出:

Alt: !Bar 'foo'
Name: !Foo 'bar'
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 使用PyYAML是不安全的load()不要使用它,没有必要(如我的代码所示)。更糟糕的是,PyYAML没有提供任何危险的反馈。
  • PyYAML会转储所有带有引号标签的标量,即使您像我一样保留节点样式(或强制使用空字符串)也是如此。为了防止这种情况的发生,您将不得不深入研究节点的序列化。我一直在ruamel.yaml程序包中进行此问题的修复,因为引号通常是不必要的。
  • 您的锚点和别名无法解析。仅仅是PyYAML不够聪明,无法在加载时扩展合并密钥。如果您在YAML中具有正常的自引用,则将在转储的YAML中获得锚点和别名。
  • 如果您的节点在标记之后不是标量(即映射或序列),则上述内容会引发错误。也可以一般地加载/转储这些文件。只需添加一些类型,并延长default_constructor了一些elif isinstance(node, yaml.MappingNode)elif isinstance(node, yaml.SequenceNode)。我将使它们创建不同的类型(行为类似于dict resp。列表),如果走那条路线,您应该意识到,构造这些类型将需要分两步进行(yield构造的对象,然后获取子对象-node值并填充对象),否则不能使用自引用结构(即节点内的别名)。
  • PyYAML不会保留映射中元素的顺序
  • 您可以有一个!CustomTag:以冒号结尾的标签,但是我发现它不太容易阅读!CustomTag: foo,因为它看起来非常像块样式映射中的键/值对。