Usando PyYAML, si leo en un archivo con valores en blanco en un dict:

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

Esto devuelve None para la clave first:

>>> data = yaml.load(test_str)
>>> data
{'attrs': {'second': 'value2', 'first': None}}

Pero al escribir, el valor None se reemplaza con null:

>>> print(yaml.dump(data, default_flow_style=False))
attrs:
  first: null
  second: value2

¿Hay alguna forma de formatear la salida de volcado para imprimir un escalar en blanco en lugar de null?

8
Michael Delgado 13 may. 2016 a las 05:08

3 respuestas

La mejor respuesta

Basado en la excelente respuesta de @ Anthon, pude crear esta solución:

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

yaml.add_representer(type(None), represent_none)

Según mi comprensión del código PyYAML, agregar un representador para un tipo existente simplemente debe reemplazar al representador existente.

Este es un cambio global y eso significa que todos los siguientes volcados usan un espacio en blanco. Si alguna otra pieza de código no relacionada en su programa se basa en None para representarse de la manera "normal", p. una biblioteca que importe y que también use PyYAML, esa biblioteca ya no funcionará de la manera esperada / correctamente, en ese caso la subclasificación es la forma correcta de hacerlo.

9
Anthon 4 abr. 2019 a las 09:11

Obtiene null porque dump() utiliza Representer(), que subclases SafeRepresenter() y para representar None, se llama al siguiente método:

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

Como la cadena null está codificada, no hay opción a dump() para cambiar eso.

La forma correcta de resolver esto en PyYAML es crear su propia subclase Dumper que tenga Emitter, Serializer y Resolver del estándar Dumper que { {X5}} utiliza, pero con la subclase de Representer que representa None de la manera que lo desee:

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)

Te dio:

attrs:
  first:
  second: value2

Si eso suena como una sobrecarga para deshacerse de null, lo es. Hay algunos atajos que puede tomar e incluso puede intentar injertar la función alternativa en el Representer existente, pero dado que la función real tomada se referencia en una tabla de búsqueda (rellenada por add_representer) necesita manejar al menos esa referencia también.

La solución mucho más fácil es reemplazar PyYAML con ruamel.yaml y usar su funcionalidad round_trip (descargo de responsabilidad: soy el autor de ese paquete):

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

Además de emitir None como el escalar vacío, también conserva el orden en las claves de mapeo, comentarios y nombres de etiquetas, ninguno de los cuales lo hace PyYAML. ruamel.yaml también sigue la especificación YAML 1.2 (de 2009), donde PyYAML usa el YAML 1.1 anterior.


El paquete ruamel.yaml se puede instalar con pip de PyPI, o con distribuciones modernas basadas en Debian, también con apt-get python-ruamel.yaml

11
Anthon 13 may. 2016 a las 05:04

Simplemente use string replace

print(yaml.dump(data).replace("null", ""))
-4
zsyh 8 ago. 2018 a las 09:12