PyYAML:加载和转储 yaml 文件并保留标签 (!CustomTag)
PyYAML: load and dump yaml file and preserve tags ( !CustomTag )
我想创建一个 YAML 过滤器来读取 YAML 文件,处理它并在之后转储它。
它必须解析任何别名(开箱即用):
>>> yaml.dump(yaml.load("""
Foo: &bar
name: bar
Foo2:
<<: *bar
"""))
'Foo: {name: bar}\nFoo2: {name: bar}\n'
但它还应保留任何类型的!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
^
我读了 pyYAML Errors on "!" in a 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"
我想应该没少缺什么,但是呢?如何加载包含任何标签的文件并在之后转储它们?
因为 default_ctor()
returns 一个字符串(它只是标签和标量的串联),这就是被转储的内容。而且因为标签以 !
开头,将该字符串转储到标量将使您得到引号。
如果你想一般地保留标签和值,你需要将它们存储在一个特殊类型中(而不是 "normal" 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'
备注:
- 使用 PyYAML 的
load()
是不安全的。 不要使用它,没有必要(如我的代码所示)。更糟糕的是,PyYAML 没有反馈任何危险。
- PyYAML 转储所有具有带引号的标记的标量,即使您像我一样保留节点样式(或强制为空字符串)。为了防止这种情况发生,您将不得不深入研究节点的序列化。我一直在我的 ruamel.yaml 包中解决这个问题,因为通常不需要引号。
- 您的锚点和别名没有得到解析。只是 PyYAML 不够聪明,除了在加载时扩展 merge key 之外什么都做不了。如果您的 YAML 中有正常的自引用,您将在转储的 YAML 中获得一个锚点和别名。
- 如果您的节点在标记之后不是标量(即映射或序列),则上述内容会很好地引发错误。 load/dump 也可以是通用的。只需添加一些类型并用一些
elif isinstance(node, yaml.MappingNode)
和 elif isinstance(node, yaml.SequenceNode)
扩展 default_constructor
。我会让那些创建不同的类型(表现得像一个 dict resp.list),如果你走那条路,你应该知道构建这些将需要在一个两步过程中发生(yield
构造的对象,然后获取子节点值并填充对象),否则不能使用自引用结构(即节点内的别名)。
- PyYAML 不保留映射中元素的顺序
- 你可以有一个以冒号结尾的标签
!CustomTag:
,但我发现它读起来不太友好 !CustomTag: foo
,因为它看起来很像一个键值对块样式映射。
已接受的答案处理仅 标量。我也需要映射类型。我想要一个通用的解决方案。我想我可能过度设计了这个。我觉得这可以更简单,欢迎进一步简化。
因此,如果您有看起来更像这样的 Yaml:
Name: !Foo bar
Alt: !Bar foo
other: !Join
- thing
- other thing
textblock: !Mangle |
This is a block
of text that
spans lines
试试这段较长的代码:
import sys
import yaml
import pprint
yaml_str = """\
Name: !Foo bar
Alt: !Bar foo
other: !Join
- thing
- other thing
textblock: !Mangle |
This is a block
of text that
spans lines
"""
class SafeUnknownConstructor(yaml.constructor.SafeConstructor):
def __init__(self):
yaml.constructor.SafeConstructor.__init__(self)
def construct_undefined(self, node):
data = getattr(self, 'construct_' + node.id)(node)
datatype = type(data)
wraptype = type('TagWrap_'+datatype.__name__, (datatype,), {})
wrapdata = wraptype(data)
wrapdata.tag = lambda: None
wrapdata.datatype = lambda: None
setattr(wrapdata, "wrapTag", node.tag)
setattr(wrapdata, "wrapType", datatype)
return wrapdata
class SafeUnknownLoader(SafeUnknownConstructor, yaml.loader.SafeLoader):
def __init__(self, stream):
SafeUnknownConstructor.__init__(self)
yaml.loader.SafeLoader.__init__(self, stream)
class SafeUnknownRepresenter(yaml.representer.SafeRepresenter):
def represent_data(self, wrapdata):
tag = False
if type(wrapdata).__name__.startswith('TagWrap_'):
datatype = getattr(wrapdata, "wrapType")
tag = getattr(wrapdata, "wrapTag")
data = datatype(wrapdata)
else:
data = wrapdata
node = super(SafeUnknownRepresenter, self).represent_data(data)
if tag:
node.tag = tag
return node
class SafeUnknownDumper(SafeUnknownRepresenter, yaml.dumper.SafeDumper):
def __init__(self, stream,
default_style=None, default_flow_style=False,
canonical=None, indent=None, width=None,
allow_unicode=None, line_break=None,
encoding=None, explicit_start=None, explicit_end=None,
version=None, tags=None, sort_keys=True):
SafeUnknownRepresenter.__init__(self, default_style=default_style,
default_flow_style=default_flow_style, sort_keys=sort_keys)
yaml.dumper.SafeDumper.__init__(self, stream,
default_style=default_style,
default_flow_style=default_flow_style,
canonical=canonical,
indent=indent,
width=width,
allow_unicode=allow_unicode,
line_break=line_break,
encoding=encoding,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
sort_keys=sort_keys)
MySafeLoader = SafeUnknownLoader
yaml.constructor.SafeConstructor.add_constructor(None, SafeUnknownConstructor.construct_undefined)
data = yaml.load(yaml_str, MySafeLoader)
pprint.pprint(data)
yaml.dump_all([data], sys.stdout, Dumper=SafeUnknownDumper, default_flow_style=False, allow_unicode=True)
输出:
{'Alt': u'foo',
'Name': u'bar',
'other': ['thing', 'other thing'],
'textblock': u'This is a block\nof text that \nspans lines\n'}
Alt: !Bar 'foo'
Name: !Foo 'bar'
other: !Join
- thing
- other thing
textblock: !Mangle "This is a block\nof text that \nspans lines\n"
注意:如果添加的代码更新数据并且不检查包装器,它可以将元素恢复为未包装的类型并丢失标签
我想创建一个 YAML 过滤器来读取 YAML 文件,处理它并在之后转储它。
它必须解析任何别名(开箱即用):
>>> yaml.dump(yaml.load("""
Foo: &bar
name: bar
Foo2:
<<: *bar
"""))
'Foo: {name: bar}\nFoo2: {name: bar}\n'
但它还应保留任何类型的!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
^
我读了 pyYAML Errors on "!" in a 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"
我想应该没少缺什么,但是呢?如何加载包含任何标签的文件并在之后转储它们?
因为 default_ctor()
returns 一个字符串(它只是标签和标量的串联),这就是被转储的内容。而且因为标签以 !
开头,将该字符串转储到标量将使您得到引号。
如果你想一般地保留标签和值,你需要将它们存储在一个特殊类型中(而不是 "normal" 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'
备注:
- 使用 PyYAML 的
load()
是不安全的。 不要使用它,没有必要(如我的代码所示)。更糟糕的是,PyYAML 没有反馈任何危险。 - PyYAML 转储所有具有带引号的标记的标量,即使您像我一样保留节点样式(或强制为空字符串)。为了防止这种情况发生,您将不得不深入研究节点的序列化。我一直在我的 ruamel.yaml 包中解决这个问题,因为通常不需要引号。
- 您的锚点和别名没有得到解析。只是 PyYAML 不够聪明,除了在加载时扩展 merge key 之外什么都做不了。如果您的 YAML 中有正常的自引用,您将在转储的 YAML 中获得一个锚点和别名。
- 如果您的节点在标记之后不是标量(即映射或序列),则上述内容会很好地引发错误。 load/dump 也可以是通用的。只需添加一些类型并用一些
elif isinstance(node, yaml.MappingNode)
和elif isinstance(node, yaml.SequenceNode)
扩展default_constructor
。我会让那些创建不同的类型(表现得像一个 dict resp.list),如果你走那条路,你应该知道构建这些将需要在一个两步过程中发生(yield
构造的对象,然后获取子节点值并填充对象),否则不能使用自引用结构(即节点内的别名)。 - PyYAML 不保留映射中元素的顺序
- 你可以有一个以冒号结尾的标签
!CustomTag:
,但我发现它读起来不太友好!CustomTag: foo
,因为它看起来很像一个键值对块样式映射。
已接受的答案处理仅 标量。我也需要映射类型。我想要一个通用的解决方案。我想我可能过度设计了这个。我觉得这可以更简单,欢迎进一步简化。
因此,如果您有看起来更像这样的 Yaml:
Name: !Foo bar
Alt: !Bar foo
other: !Join
- thing
- other thing
textblock: !Mangle |
This is a block
of text that
spans lines
试试这段较长的代码:
import sys
import yaml
import pprint
yaml_str = """\
Name: !Foo bar
Alt: !Bar foo
other: !Join
- thing
- other thing
textblock: !Mangle |
This is a block
of text that
spans lines
"""
class SafeUnknownConstructor(yaml.constructor.SafeConstructor):
def __init__(self):
yaml.constructor.SafeConstructor.__init__(self)
def construct_undefined(self, node):
data = getattr(self, 'construct_' + node.id)(node)
datatype = type(data)
wraptype = type('TagWrap_'+datatype.__name__, (datatype,), {})
wrapdata = wraptype(data)
wrapdata.tag = lambda: None
wrapdata.datatype = lambda: None
setattr(wrapdata, "wrapTag", node.tag)
setattr(wrapdata, "wrapType", datatype)
return wrapdata
class SafeUnknownLoader(SafeUnknownConstructor, yaml.loader.SafeLoader):
def __init__(self, stream):
SafeUnknownConstructor.__init__(self)
yaml.loader.SafeLoader.__init__(self, stream)
class SafeUnknownRepresenter(yaml.representer.SafeRepresenter):
def represent_data(self, wrapdata):
tag = False
if type(wrapdata).__name__.startswith('TagWrap_'):
datatype = getattr(wrapdata, "wrapType")
tag = getattr(wrapdata, "wrapTag")
data = datatype(wrapdata)
else:
data = wrapdata
node = super(SafeUnknownRepresenter, self).represent_data(data)
if tag:
node.tag = tag
return node
class SafeUnknownDumper(SafeUnknownRepresenter, yaml.dumper.SafeDumper):
def __init__(self, stream,
default_style=None, default_flow_style=False,
canonical=None, indent=None, width=None,
allow_unicode=None, line_break=None,
encoding=None, explicit_start=None, explicit_end=None,
version=None, tags=None, sort_keys=True):
SafeUnknownRepresenter.__init__(self, default_style=default_style,
default_flow_style=default_flow_style, sort_keys=sort_keys)
yaml.dumper.SafeDumper.__init__(self, stream,
default_style=default_style,
default_flow_style=default_flow_style,
canonical=canonical,
indent=indent,
width=width,
allow_unicode=allow_unicode,
line_break=line_break,
encoding=encoding,
explicit_start=explicit_start,
explicit_end=explicit_end,
version=version,
tags=tags,
sort_keys=sort_keys)
MySafeLoader = SafeUnknownLoader
yaml.constructor.SafeConstructor.add_constructor(None, SafeUnknownConstructor.construct_undefined)
data = yaml.load(yaml_str, MySafeLoader)
pprint.pprint(data)
yaml.dump_all([data], sys.stdout, Dumper=SafeUnknownDumper, default_flow_style=False, allow_unicode=True)
输出:
{'Alt': u'foo',
'Name': u'bar',
'other': ['thing', 'other thing'],
'textblock': u'This is a block\nof text that \nspans lines\n'}
Alt: !Bar 'foo'
Name: !Foo 'bar'
other: !Join
- thing
- other thing
textblock: !Mangle "This is a block\nof text that \nspans lines\n"
注意:如果添加的代码更新数据并且不检查包装器,它可以将元素恢复为未包装的类型并丢失标签