En Python, comment charger YAML mappings comme OrderedDicts?
je voudrais obtenir PyYAML loader pour charger les mappages (et commandé mappings) dans le Python 2.7+ OrderedDict de type, au lieu de la vanille dict
et la liste des couples qu'il utilise actuellement.
Quelle est la meilleure façon de faire ça?
9 réponses
mise à jour: en Python 3.6+ vous n'avez probablement pas besoin de OrderedDict
du tout en raison de la nouvelle implémentation dict qui a été utilisé dans pypy depuis un certain temps (bien que considéré CPython détails d'implémentation pour le moment).
mise à Jour: En python de 3,7+, l'insertion-la conservation de l'ordre de la nature du dict des objets a été déclarée officiellement partie du langage Python spec , voir Quoi de neuf en Python 3.7 .
j'aime @James' solution pour sa simplicité. Cependant, il modifie la classe globale par défaut yaml.Loader
, qui peut conduire à des effets secondaires gênants. En particulier, en écrivant le code de la bibliothèque, c'est une mauvaise idée. De plus, il ne fonctionne pas directement avec yaml.safe_load()
.
heureusement, la solution peut être améliorée sans trop d'effort:
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)
pour la sérialisation, Je ne connais pas de généralisation évidente, mais au moins cela ne devrait pas avoir d'effets secondaires:
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)
le module yaml vous permet de spécifier des 'représentants' personnalisés pour convertir des objets Python en texte et des 'constructeurs' pour inverser le processus.
_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)
2018 option:
oyaml
est un remplacement direct pour PyYAML qui préserve la commande de dict. Python 2 et Python 3 sont supportés. Juste pip install oyaml
, et importer comme indiqué ci-dessous:
import oyaml as yaml
vous ne serez plus ennuyé par des mappages ratés lors du déchargement/chargement.
Note: je suis l'auteur de oyaml.
2015 (et versions ultérieures) option:
ruamel.yaml est une baisse de remplacement pour PyYAML (disclaimer: je suis l'auteur de ce forfait). Préserver l'ordre des mappings était l'une des choses ajoutées dans la première version (0.1) en 2015. Non seulement il préserve l'ordre de vos dictionnaires, mais il préserve aussi les commentaires, les noms d'ancrage, les étiquettes et il supporte la spécification YAML 1.2 (publié en 2009)
le la spécification dit que la commande n'est pas garantie, mais bien sûr il y a la commande dans le fichier YAML et l'analyseur approprié peut simplement s'y tenir et générer de manière transparente un objet qui garde la commande. Vous avez juste besoin de choisir le bon parser, chargeur et dumper1:
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)
vous donnera:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
données est de type CommentedMap qui fonctionne comme un dict, mais a des informations supplémentaires qui est maintenu autour de jusqu'à être jetés (y compris le commentaire préservé!)
Note : il existe une bibliothèque, basée sur la réponse suivante, qui implémente également le CLoader et les CDumpers: Phynix / yamlloader
je doute fort que c'est la meilleure façon de le faire, mais c'est la façon dont je suis venu avec, et ça fonctionne. Aussi disponible comme un gist .
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
Update : la bibliothèque a été dépréciée en faveur du yamlloader (qui est basé sur le yamlordereddictloader)
je viens de trouver une bibliothèque Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) qui a été créé à partir des réponses à cette question et est assez simple à utiliser:
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
sur mon installation pour PyYaml pour Python 2.7 j'ai mis à jour __init__.py, constructor.py, et loader.py. Prend maintenant en charge l'option object_pairs_hook pour les commandes load. Diff de modifications que j'ai apportées ci-dessous.
__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)
il y a un billet PyYAML sur le sujet ouvert il y a 5 ans. Il contient quelques liens pertinents, y compris le lien à cette question :) j'ai personnellement saisi gist 317164 et l'ai modifié un peu pour utiliser OrderedDict de Python 2.7, pas l'implémentation incluse (vient de remplacer la classe par from collections import OrderedDict
).
voici une solution simple qui vérifie également les clés de niveau supérieur dupliquées dans votre carte.
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])