J'aimerais que le programme PyYAML charge les mappages (et les mappages commandés) dans le type Python 2.7+ OrderedDict , à la place de la variable Vanilla dict
et de la liste des paires qu'il utilise actuellement.
Quelle est la meilleure façon de faire ça?
Note: il existe une bibliothèque, basée sur la réponse suivante, qui implémente également le CLoader et le CDumpers: Phynix/yamlloader
Je doute fort que ce soit la meilleure façon de le faire, mais c’est ce que j’ai trouvé, et ça marche. Aussi disponible en tant que 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: Dans python 3.6+, vous n'avez probablement pas besoin de OrderedDict
du fait de new dict implementation qui est utilisé dans pypy depuis un certain temps (bien que considéré pour le moment comme une implémentation de CPython).
Mise à jour: Dans python 3.7+, la nature de la préservation de l'ordre d'insertion des objets dict a été déclarée partie officielle de la spécification du langage Python}, voir Quoi de neuf dans Python 3.7 .
J'aime @James ' solution pour sa simplicité. Cependant, la classe yaml.Loader
globale par défaut est modifiée, ce qui peut entraîner des effets secondaires gênants. En particulier, lorsque vous écrivez du code de bibliothèque, c'est une mauvaise idée. En outre, cela 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 les 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)
oyaml
est un remplacement instantané de PyYAML qui préserve l’ordre des dict. Python 2 et Python 3 sont tous deux pris en charge. Juste pip install oyaml
, et importez comme indiqué ci-dessous:
import oyaml as yaml
Vous ne serez plus dérangé par les correspondances gâchées lors du déchargement/chargement.
Note: Je suis l'auteur d'oyaml.
ruamel.yaml est une solution de remplacement à PyYAML (disclaimer: je suis l'auteur de ce paquet). La préservation de l'ordre des mappages a été l'une des choses ajoutées dans la première version (0.1) en 2015. Cela préserve non seulement l'ordre des dictionnaires, mais également les commentaires, les noms d'ancres, les balises et prend en charge YAML 1.2. spécification (publié en 2009)
La spécification indique que la commande n'est pas garantie, mais il existe bien sûr une commande dans le fichier YAML et l'analyseur approprié peut simplement conserver cette information et générer de manière transparente un objet qui conserve la commande. Il vous suffit de choisir le bon analyseur, chargeur et dumpeur¹:
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 donnera:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
data
est du type CommentedMap
qui fonctionne comme un dict, mais contient des informations supplémentaires qui sont conservées jusqu’à ce qu’elles soient vidées (y compris le commentaire préservé!)
Update: la bibliothèque a été déconseillée au profit de yamlloader (basé sur yamlordereddictloader)
Je viens de trouver une bibliothèque Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) qui a été créée à 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 For 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 de chargement. Diff des modifications que j'ai apportées est 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, notamment le lien vers cette question :) J'ai personnellement saisi Gist 317164 et l'ai modifié un peu pour utiliser OrderedDict à partir de Python 2.7, pas l'implémentation incluse avec 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])