lib - En Python, ¿cómo puedes cargar las asignaciones de YAML como OrderedDicts?
yaml in python (9)
Opción 2018:
oyaml
es un reemplazo PyYAML para PyYAML que preserva la ordenación de dict. Tanto Python 2 como Python 3 son compatibles. Simplemente pip install oyaml
, e importe como se muestra a continuación:
import oyaml as yaml
Ya no te molestarán las asignaciones estropeadas al descargar / cargar.
Nota: soy el autor de oyaml.
Me gustaría que el cargador de PyYAML cargue asignaciones (y asignaciones ordenadas) en el tipo OrderedDict Python 2.7+, en lugar del dict
vanilla y la lista de pares que usa actualmente.
¿Cuál es la mejor manera de hacer eso?
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])
El módulo yaml le permite especificar ''representantes'' personalizados para convertir objetos de Python a texto y ''constructores'' para invertir 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)
En mi instalación For PyYaml para Python 2.7 actualicé __init__.py, constructor.py y loader.py. Ahora es compatible con la opción object_pairs_hook para cargar comandos. La diferencia de los cambios que hice está debajo.
__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)
Hay un boleto PyYAML sobre el tema abierto hace 5 años. Contiene algunos enlaces relevantes, incluido el enlace a esta misma pregunta :) Personalmente tomé gist 317164 y lo modifiqué un poco para usar OrderedDict de Python 2.7, no la implementación incluida (solo reemplacé la clase con from collections import OrderedDict
).
La especificación dice que el orden no está garantizado, pero por supuesto hay un pedido en el archivo YAML y el analizador apropiado puede aferrarse a eso y generar de forma transparente un objeto que mantiene el orden. Solo tiene que elegir el analizador, el cargador y el dumper¹ adecuados:
import sys
import ruamel.yaml as yaml
yaml_str = """/
3: abc
conf:
10: def
3: gij # h is missing
more:
- what
- else
"""
data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
data[''conf''][10] = ''klm''
data[''conf''][3] = ''jig''
yaml.dump(data, sys.stdout, Dumper=yaml.RoundTripDumper)
Te regalaré:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
los datos son del tipo CommentedMap que funciona como un dict, pero tiene información adicional que se guarda hasta que es objeto de dumping (¡incluido el comentario conservado!)
¹ Esto fue hecho usando ruamel.yaml del cual yo soy el autor. Es un tenedor y superconjunto de PyYAML.
Actualización: para Python 3.6+ probablemente no necesite nada especial debido a la nueva implementación de dict (aunque por el momento se considera el detalle de implementación de CPython).
Me gusta la solution de @James por su simplicidad. Sin embargo, cambia la clase yaml.Loader
global predeterminada, lo que puede ocasionar efectos secundarios problemáticos. Especialmente, cuando se escribe 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 sé 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)
Actualización : la biblioteca ha quedado obsoleta a favor del Phynix/yamlloader (que se basa en el cargador yamlordereddict)
Acabo de encontrar una biblioteca de Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) que se creó en función de las respuestas a esta pregunta y es bastante simple de usar:
import yaml
import yamlordereddictloader
datas = yaml.load(open(''myfile.yml''), Loader=yamlordereddictloader.Loader)
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 esta es la forma en que 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