Me gustaría obtener el cargador de PyYAML para cargar asignaciones (y asignaciones ordenadas) en el tipo OrderedDict de Python 2.7+ , en lugar del vainilla dict
y la lista de pares que usa actualmente.
¿Cuál es la mejor manera de hacer eso?
Me gustaría obtener el cargador de PyYAML para cargar asignaciones (y asignaciones ordenadas) en el tipo OrderedDict de Python 2.7+ , en lugar del vainilla dict
y la lista de pares que usa actualmente.
¿Cuál es la mejor manera de hacer eso?
Respuestas:
Actualización: en python 3.6+ probablemente no necesite OrderedDict
nada debido a la nueva implementación de dict que ha estado en uso en pypy durante algún tiempo (aunque se considera detalles de implementación de CPython por ahora).
Actualización: en python 3.7+, la naturaleza de preservación del orden de inserción de los objetos dict se ha declarado como una parte oficial de la especificación del lenguaje Python , consulte Novedades de Python 3.7 .
Me gusta la solución de @James por su simplicidad. Sin embargo, cambia la yaml.Loader
clase global predeterminada , lo que puede provocar efectos secundarios problemáticos. Especialmente, al escribir código de biblioteca, esta es una mala idea. Además, no funciona directamente con yaml.safe_load()
.
Afortunadamente, la solución se puede mejorar sin mucho esfuerzo:
import yaml
from collections import OrderedDict
def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict):
class OrderedLoader(Loader):
pass
def construct_mapping(loader, node):
loader.flatten_mapping(node)
return object_pairs_hook(loader.construct_pairs(node))
OrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
construct_mapping)
return yaml.load(stream, OrderedLoader)
# usage example:
ordered_load(stream, yaml.SafeLoader)
Para la serialización, no conozco una generalización obvia, pero al menos esto no debería tener ningún efecto secundario:
def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
class OrderedDumper(Dumper):
pass
def _dict_representer(dumper, data):
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
data.items())
OrderedDumper.add_representer(OrderedDict, _dict_representer)
return yaml.dump(data, stream, OrderedDumper, **kwds)
# usage:
ordered_dump(data, Dumper=yaml.SafeDumper)
El módulo yaml le permite especificar 'representantes' personalizados para convertir objetos de Python en texto y 'constructores' para revertir el proceso.
_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
def dict_representer(dumper, data):
return dumper.represent_dict(data.iteritems())
def dict_constructor(loader, node):
return collections.OrderedDict(loader.construct_pairs(node))
yaml.add_representer(collections.OrderedDict, dict_representer)
yaml.add_constructor(_mapping_tag, dict_constructor)
from six import iteritems
y luego cambiarlo para iteritems(data)
que funcione igual de bien en Python 2 y 3.
represent_dict
y DEFAULT_MAPPING_TAG
). ¿Esto se debe a que la documentación está incompleta o estas funciones no son compatibles y están sujetas a cambios sin previo aviso?
dict_constructor
tendrá que llamar loader.flatten_mapping(node)
o no podrá cargar <<: *...
(combinar la sintaxis)
oyaml
es un reemplazo directo para PyYAML que conserva el pedido de dict. Python 2 y Python 3 son compatibles. Solo pip install oyaml
, e importe como se muestra a continuación:
import oyaml as yaml
Ya no te molestarán las asignaciones arruinadas al descargar / cargar.
Nota: soy el autor de oyaml.
ruamel.yaml es una caída en el reemplazo de PyYAML (descargo de responsabilidad: soy el autor de ese paquete). Preservar el orden de las asignaciones fue una de las cosas que se agregaron en la primera versión (0.1) en 2015. No solo conserva el orden de los diccionarios, sino que también conserva los comentarios, los nombres de anclaje, las etiquetas y es compatible con YAML 1.2 especificación (lanzado en 2009)
La especificación dice que el orden no está garantizado, pero, por supuesto, hay un orden en el archivo YAML y el analizador apropiado puede retenerlo y generar de forma transparente un objeto que mantenga el orden. Solo tiene que elegir el analizador, el cargador y el volcador correctos¹:
import sys
from ruamel.yaml import YAML
yaml_str = """\
3: abc
conf:
10: def
3: gij # h is missing
more:
- what
- else
"""
yaml = YAML()
data = yaml.load(yaml_str)
data['conf'][10] = 'klm'
data['conf'][3] = 'jig'
yaml.dump(data, sys.stdout)
Te regalaré:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
data
es de tipo CommentedMap
que funciona como un dict, pero tiene información adicional que se mantiene hasta que se descarta (¡incluido el comentario conservado!)
CommentedMap
directamente pero no funciona, y lo OrderedDict
coloca en !!omap
todas partes, lo que no es muy fácil de usar.
CommentedMap
con safe=True
in YAML
, que no funcionó (usando safe=False
works). También tuve problemas para CommentedMap
no ser modificable, pero no puedo reproducirlo ahora ... Abriré una nueva pregunta si encuentro este problema nuevamente.
yaml = YAML()
, obtienes el analizador / volcador de ida y vuelta y eso es un derivado del analizador / volcador seguro que sabe sobre CommentedMap / Seq, etc.
Nota : hay una biblioteca, basada en la siguiente respuesta, que implementa también CLoader y CDumpers: Phynix / yamlloader
Dudo mucho que esta sea la mejor manera de hacerlo, pero así es como se me ocurrió, y funciona. También disponible como una esencia .
import yaml
import yaml.constructor
try:
# included in standard lib from Python 2.7
from collections import OrderedDict
except ImportError:
# try importing the backported drop-in replacement
# it's available on PyPI
from ordereddict import OrderedDict
class OrderedDictYAMLLoader(yaml.Loader):
"""
A YAML loader that loads mappings into ordered dictionaries.
"""
def __init__(self, *args, **kwargs):
yaml.Loader.__init__(self, *args, **kwargs)
self.add_constructor(u'tag:yaml.org,2002:map', type(self).construct_yaml_map)
self.add_constructor(u'tag:yaml.org,2002:omap', type(self).construct_yaml_map)
def construct_yaml_map(self, node):
data = OrderedDict()
yield data
value = self.construct_mapping(node)
data.update(value)
def construct_mapping(self, node, deep=False):
if isinstance(node, yaml.MappingNode):
self.flatten_mapping(node)
else:
raise yaml.constructor.ConstructorError(None, None,
'expected a mapping node, but found %s' % node.id, node.start_mark)
mapping = OrderedDict()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=deep)
try:
hash(key)
except TypeError, exc:
raise yaml.constructor.ConstructorError('while constructing a mapping',
node.start_mark, 'found unacceptable key (%s)' % exc, key_node.start_mark)
value = self.construct_object(value_node, deep=deep)
mapping[key] = value
return mapping
key_node.start_mark
atributo en su mensaje de error, no veo ninguna forma obvia de simplificar su ciclo de construcción central. Si intenta utilizar el hecho de que el OrderedDict
constructor aceptará un iterable de pares clave, valor, perderá el acceso a ese detalle al generar el mensaje de error.
add_constructor
su __init__
método.
Actualización : la biblioteca quedó en desuso a favor del yamlloader (que se basa en el yamlordereddictloader)
Acabo de encontrar una biblioteca de Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) que se creó en base a las respuestas a esta pregunta y es bastante simple de usar:
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
yodl
github.
En mi instalación de For PyYaml para Python 2.7, actualicé __init__.py, constructor.py y loader.py. Ahora admite la opción object_pairs_hook para los comandos de carga. La diferencia de los cambios que hice está a continuación.
__init__.py
$ diff __init__.py Original
64c64
< def load(stream, Loader=Loader, **kwds):
---
> def load(stream, Loader=Loader):
69c69
< loader = Loader(stream, **kwds)
---
> loader = Loader(stream)
75c75
< def load_all(stream, Loader=Loader, **kwds):
---
> def load_all(stream, Loader=Loader):
80c80
< loader = Loader(stream, **kwds)
---
> loader = Loader(stream)
constructor.py
$ diff constructor.py Original
20,21c20
< def __init__(self, object_pairs_hook=dict):
< self.object_pairs_hook = object_pairs_hook
---
> def __init__(self):
27,29d25
< def create_object_hook(self):
< return self.object_pairs_hook()
<
54,55c50,51
< self.constructed_objects = self.create_object_hook()
< self.recursive_objects = self.create_object_hook()
---
> self.constructed_objects = {}
> self.recursive_objects = {}
129c125
< mapping = self.create_object_hook()
---
> mapping = {}
400c396
< data = self.create_object_hook()
---
> data = {}
595c591
< dictitems = self.create_object_hook()
---
> dictitems = {}
602c598
< dictitems = value.get('dictitems', self.create_object_hook())
---
> dictitems = value.get('dictitems', {})
loader.py
$ diff loader.py Original
13c13
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
18c18
< BaseConstructor.__init__(self, **constructKwds)
---
> BaseConstructor.__init__(self)
23c23
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
28c28
< SafeConstructor.__init__(self, **constructKwds)
---
> SafeConstructor.__init__(self)
33c33
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
38c38
< Constructor.__init__(self, **constructKwds)
---
> Constructor.__init__(self)
Aquí hay una solución simple que también busca claves duplicadas de nivel superior en su mapa.
import yaml
import re
from collections import OrderedDict
def yaml_load_od(fname):
"load a yaml file as an OrderedDict"
# detects any duped keys (fail on this) and preserves order of top level keys
with open(fname, 'r') as f:
lines = open(fname, "r").read().splitlines()
top_keys = []
duped_keys = []
for line in lines:
m = re.search(r'^([A-Za-z0-9_]+) *:', line)
if m:
if m.group(1) in top_keys:
duped_keys.append(m.group(1))
else:
top_keys.append(m.group(1))
if duped_keys:
raise Exception('ERROR: duplicate keys: {}'.format(duped_keys))
# 2nd pass to set up the OrderedDict
with open(fname, 'r') as f:
d_tmp = yaml.load(f)
return OrderedDict([(key, d_tmp[key]) for key in top_keys])