我可以在yaml / pyyaml中转储空白而不是null吗?

时间:2016-05-13 02:08:46

标签: python yaml pyyaml

使用PyYAML,如果我在dict中读入一个空白值的文件:

test_str = '''
attrs:
  first:
  second: value2
'''

这会为键None返回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

4 个答案:

答案 0 :(得分:8)

得到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中的SerializerResolverDumper } dump()使用,但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上,但由于在查找表中引用了实际函数(由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作为空标量发出之外,它还保留映射键,注释和标记名称的顺序,PyYAML没有。 ruamel.yaml也遵循YAML 1.2规范(从2009年开始),其中PyYAML使用较旧的YAML 1.1。

ruamel.yaml包可以与PyPI的pip一起安装,也可以与现代的基于Debian的发行版一起安装apt-get python-ruamel.yaml

答案 1 :(得分:5)

基于@ Anthon的excellent answer,我能够制作出这个解决方案:

def represent_none(self, _):
    return self.represent_scalar('tag:yaml.org,2002:null', '')

yaml.add_representer(type(None), represent_none)

根据我对PyYAML code的理解,为现有类型添加一个代表应该只需替换现有的代表。

这是全局更改,这意味着所有后续转储都使用空白。如果您的程序中某些不相关的其他代码依赖于None以“正常”方式表示,例如您导入的库以及使用PyYAML的库,该库将不再以正确的方式正确工作,在这种情况下,子类化是正确的方法。

答案 2 :(得分:1)

在解决@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)

答案 3 :(得分:-2)

只需使用字符串替换

print(yaml.dump(data).replace("null", ""))