我可以在 yaml/pyyaml 中转储空白而不是 null 吗?
Can I dump blank instead of null in yaml/pyyaml?
使用 PyYAML,如果我读入一个字典中包含空白值的文件:
test_str = '''
attrs:
first:
second: value2
'''
这个returnsNone
为键first
:
>>> data = yaml.load(test_str)
>>> data
{'attrs': {'second': 'value2', 'first': None}}
但是写的时候,None
值被替换成null
:
>>> print(yaml.dump(data, default_flow_style=False))
attrs:
first: null
second: value2
有没有办法格式化转储输出以打印空白标量而不是 null
?
你得到null
是因为dump()
使用了Representer()
的子类SafeRepresenter()
来表示None
,下面的方法被调用:
def represent_none(self, data):
return self.represent_scalar(u'tag:yaml.org,2002:null',
u'null')
由于字符串 null
是硬编码的,因此无法选择 dump()
来更改它。
在 PyYAML 中解决此问题的正确方法是创建您自己的 Dumper
子类,其中包含来自标准 [=] 的 Emitter
、Serializer
和 Resolver
dump()
使用的 21=],但是 Representer
的子类代表 None
您想要的方式:
import sys
import yaml
from yaml.representer import Representer
from yaml.dumper import Dumper
from yaml.emitter import Emitter
from yaml.serializer import Serializer
from yaml.resolver import Resolver
yaml_str = """\
attrs:
first:
second: value2
"""
class MyRepresenter(Representer):
def represent_none(self, data):
return self.represent_scalar(u'tag:yaml.org,2002:null',
u'')
class MyDumper(Emitter, Serializer, MyRepresenter, Resolver):
def __init__(self, stream,
default_style=None, default_flow_style=None,
canonical=None, indent=None, width=None,
allow_unicode=None, line_break=None,
encoding=None, explicit_start=None, explicit_end=None,
version=None, tags=None):
Emitter.__init__(self, stream, canonical=canonical,
indent=indent, width=width,
allow_unicode=allow_unicode, line_break=line_break)
Serializer.__init__(self, encoding=encoding,
explicit_start=explicit_start, explicit_end=explicit_end,
version=version, tags=tags)
MyRepresenter.__init__(self, default_style=default_style,
default_flow_style=default_flow_style)
Resolver.__init__(self)
MyRepresenter.add_representer(type(None),
MyRepresenter.represent_none)
data = yaml.load(yaml_str)
yaml.dump(data, stream=sys.stdout, Dumper=MyDumper, default_flow_style=False)
给你:
attrs:
first:
second: value2
如果这听起来像是为了摆脱 null
的大量开销,那确实如此。您可以采取一些捷径,甚至可以尝试将替代函数移植到现有的 Representer
上,但由于实际采用的函数在查找 table 中被引用(由 add_representer
填充) 您至少还需要处理该引用。
更简单的解决方案是用 ruamel.yaml
替换 PyYAML 并使用其 round_trip 功能(免责声明:我是该软件包的作者):
import ruamel.yaml
yaml_str = """\
# trying to round-trip preserve empty scalar
attrs:
first:
second: value2
"""
data = ruamel.yaml.round_trip_load(yaml_str)
assert ruamel.yaml.round_trip_dump(data) == yaml_str
除了将 None
作为空标量发出外,它还保留映射键、注释和标签名称的顺序,none PyYAML 会这样做。 ruamel.yaml
也遵循 YAML 1.2 规范(自 2009 年起),其中 PyYAML 使用较旧的 YAML 1.1。
ruamel.yaml
包可以用 PyPI 的 pip
安装,或者用现代的基于 Debian 的发行版,也可以用 apt-get python-ruamel.yaml
基于@Anthon 的,我能够设计出这个解决方案:
def represent_none(self, _):
return self.represent_scalar('tag:yaml.org,2002:null', '')
yaml.add_representer(type(None), represent_none)
根据我对 PyYAML code 的理解,为现有类型添加一个表示器应该只是替换现有的表示器。
这是一个全局更改,这意味着所有后续转储都使用空白。如果程序中一些不相关的其他代码依赖于 None
以 "normal" 方式表示,例如您导入的库也使用 PyYAML,该库将不再适用于预期 way/correctly,在这种情况下,子类化是正确的方法。
只需使用字符串替换
print(yaml.dump(data).replace("null", ""))
在解决@Anthon 的问题时扩展@Jace Browning 的回答,我们可以使用上下文管理器,它会记住 None:
的先前实现
class BlankNone(Representer):
"""Print None as blank when used as context manager"""
def represent_none(self, *_):
return self.represent_scalar(u'tag:yaml.org,2002:null', u'')
def __enter__(self):
self.prior = Dumper.yaml_representers[type(None)]
yaml.add_representer(type(None), self.represent_none)
def __exit__(self, exc_type, exc_val, exc_tb):
Dumper.yaml_representers[type(None)] = self.prior
可以这样使用:
with BlankNone(), open(file, 'wt') as f:
yaml.dump(hosts, f)
使用 PyYAML,如果我读入一个字典中包含空白值的文件:
test_str = '''
attrs:
first:
second: value2
'''
这个returnsNone
为键first
:
>>> data = yaml.load(test_str)
>>> data
{'attrs': {'second': 'value2', 'first': None}}
但是写的时候,None
值被替换成null
:
>>> print(yaml.dump(data, default_flow_style=False))
attrs:
first: null
second: value2
有没有办法格式化转储输出以打印空白标量而不是 null
?
你得到null
是因为dump()
使用了Representer()
的子类SafeRepresenter()
来表示None
,下面的方法被调用:
def represent_none(self, data):
return self.represent_scalar(u'tag:yaml.org,2002:null',
u'null')
由于字符串 null
是硬编码的,因此无法选择 dump()
来更改它。
在 PyYAML 中解决此问题的正确方法是创建您自己的 Dumper
子类,其中包含来自标准 [=] 的 Emitter
、Serializer
和 Resolver
dump()
使用的 21=],但是 Representer
的子类代表 None
您想要的方式:
import sys
import yaml
from yaml.representer import Representer
from yaml.dumper import Dumper
from yaml.emitter import Emitter
from yaml.serializer import Serializer
from yaml.resolver import Resolver
yaml_str = """\
attrs:
first:
second: value2
"""
class MyRepresenter(Representer):
def represent_none(self, data):
return self.represent_scalar(u'tag:yaml.org,2002:null',
u'')
class MyDumper(Emitter, Serializer, MyRepresenter, Resolver):
def __init__(self, stream,
default_style=None, default_flow_style=None,
canonical=None, indent=None, width=None,
allow_unicode=None, line_break=None,
encoding=None, explicit_start=None, explicit_end=None,
version=None, tags=None):
Emitter.__init__(self, stream, canonical=canonical,
indent=indent, width=width,
allow_unicode=allow_unicode, line_break=line_break)
Serializer.__init__(self, encoding=encoding,
explicit_start=explicit_start, explicit_end=explicit_end,
version=version, tags=tags)
MyRepresenter.__init__(self, default_style=default_style,
default_flow_style=default_flow_style)
Resolver.__init__(self)
MyRepresenter.add_representer(type(None),
MyRepresenter.represent_none)
data = yaml.load(yaml_str)
yaml.dump(data, stream=sys.stdout, Dumper=MyDumper, default_flow_style=False)
给你:
attrs:
first:
second: value2
如果这听起来像是为了摆脱 null
的大量开销,那确实如此。您可以采取一些捷径,甚至可以尝试将替代函数移植到现有的 Representer
上,但由于实际采用的函数在查找 table 中被引用(由 add_representer
填充) 您至少还需要处理该引用。
更简单的解决方案是用 ruamel.yaml
替换 PyYAML 并使用其 round_trip 功能(免责声明:我是该软件包的作者):
import ruamel.yaml
yaml_str = """\
# trying to round-trip preserve empty scalar
attrs:
first:
second: value2
"""
data = ruamel.yaml.round_trip_load(yaml_str)
assert ruamel.yaml.round_trip_dump(data) == yaml_str
除了将 None
作为空标量发出外,它还保留映射键、注释和标签名称的顺序,none PyYAML 会这样做。 ruamel.yaml
也遵循 YAML 1.2 规范(自 2009 年起),其中 PyYAML 使用较旧的 YAML 1.1。
ruamel.yaml
包可以用 PyPI 的 pip
安装,或者用现代的基于 Debian 的发行版,也可以用 apt-get python-ruamel.yaml
基于@Anthon 的
def represent_none(self, _):
return self.represent_scalar('tag:yaml.org,2002:null', '')
yaml.add_representer(type(None), represent_none)
根据我对 PyYAML code 的理解,为现有类型添加一个表示器应该只是替换现有的表示器。
这是一个全局更改,这意味着所有后续转储都使用空白。如果程序中一些不相关的其他代码依赖于 None
以 "normal" 方式表示,例如您导入的库也使用 PyYAML,该库将不再适用于预期 way/correctly,在这种情况下,子类化是正确的方法。
只需使用字符串替换
print(yaml.dump(data).replace("null", ""))
在解决@Anthon 的问题时扩展@Jace Browning 的回答,我们可以使用上下文管理器,它会记住 None:
的先前实现class BlankNone(Representer):
"""Print None as blank when used as context manager"""
def represent_none(self, *_):
return self.represent_scalar(u'tag:yaml.org,2002:null', u'')
def __enter__(self):
self.prior = Dumper.yaml_representers[type(None)]
yaml.add_representer(type(None), self.represent_none)
def __exit__(self, exc_type, exc_val, exc_tb):
Dumper.yaml_representers[type(None)] = self.prior
可以这样使用:
with BlankNone(), open(file, 'wt') as f:
yaml.dump(hosts, f)