J'aimerais que le chargeur de PyYAML charge les mappages (et les mappages ordonnés) dans le type OrderedDict Python 2.7+ , au lieu de la vanille dict
et de la liste des paires qu'il utilise actuellement.
Quelle est la meilleure façon de faire cela?
J'aimerais que le chargeur de PyYAML charge les mappages (et les mappages ordonnés) dans le type OrderedDict Python 2.7+ , au lieu de la vanille dict
et de la liste des paires qu'il utilise actuellement.
Quelle est la meilleure façon de faire cela?
Réponses:
Mise à jour: Dans python 3.6+, vous n'avez probablement pas besoin OrderedDict
du tout en raison de la nouvelle implémentation de dict qui est utilisée dans pypy depuis un certain temps (bien que considérée comme un détail de l'implémentation CPython pour l'instant).
Mise à jour: dans python 3.7+, la nature de préservation de l'ordre d'insertion des objets dict a été déclarée comme faisant partie officielle de la spécification du langage Python , voir Nouveautés de Python 3.7 .
J'aime la solution de @James pour sa simplicité. Cependant, cela change la yaml.Loader
classe globale par défaut , ce qui peut entraîner des effets secondaires gênants. Surtout, lors de l'écriture de code de bibliothèque, c'est une mauvaise idée. De plus, cela ne fonctionne pas directement avec yaml.safe_load()
.
Heureusement, la solution peut être améliorée sans trop d'efforts:
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)
from six import iteritems
, puis changez-le pour iteritems(data)
qu'il fonctionne aussi bien en Python 2 et 3.
represent_dict
et DEFAULT_MAPPING_TAG
). Est-ce parce que la documentation est incomplète ou est-ce que ces fonctionnalités ne sont pas prises en charge et sont susceptibles d'être modifiées sans préavis?
dict_constructor
vous devrez appeler loader.flatten_mapping(node)
ou vous ne pourrez pas charger <<: *...
(syntaxe de fusion)
oyaml
est un remplacement de PyYAML qui préserve l'ordre des dict. Python 2 et Python 3 sont pris en charge. Juste pip install oyaml
et importez comme indiqué ci-dessous:
import oyaml as yaml
Vous ne serez plus ennuyé par les mappages foirés lors du déchargement / chargement.
Remarque: je suis l'auteur d'oyaml.
ruamel.yaml remplace PyYAML (avertissement: je suis l'auteur de ce package). Préserver l'ordre des mappages était l'une des choses ajoutées dans la première version (0.1) en 2015. Non seulement cela préserve l'ordre de vos dictionnaires, mais il conservera également les commentaires, les noms d'ancrage, les balises et prend en charge le YAML 1.2 spécification (publiée en 2009)
La spécification dit que l'ordre n'est pas garanti, mais bien sûr, il y a un ordre dans le fichier YAML et l'analyseur approprié peut simplement s'en tenir à cela et générer de manière transparente un objet qui conserve l'ordre. Il vous suffit de choisir le bon analyseur, chargeur et dumper¹:
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 a des informations supplémentaires qui sont conservées jusqu'à ce qu'elles soient vidées (y compris le commentaire conservé!)
CommentedMap
directement mais ça ne marche pas, et OrderedDict
met !!omap
partout ce qui n'est pas très convivial.
CommentedMap
avec safe=True
in YAML
, qui n'a pas fonctionné (en utilisant des safe=False
œuvres). J'ai également eu un problème de CommentedMap
ne pas être modifiable, mais je ne peux pas le reproduire maintenant ... J'ouvrirai une nouvelle question si je rencontre à nouveau ce problème.
yaml = YAML()
, vous obtenez l'analyseur / dumper aller-retour et qui est dérivé de l'analyseur / dumper sûr qui connaît CommentedMap / Seq etc.
Remarque : 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 ce soit la meilleure façon de le faire, mais c'est ainsi que j'ai proposé et cela fonctionne. Également disponible sous forme de résumé .
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
attribut dans votre message d'erreur, je ne vois aucun moyen évident de simplifier votre boucle de construction centrale. Si vous essayez d'utiliser le fait que le OrderedDict
constructeur acceptera un itérable de paires clé / valeur, vous perdez l'accès à ce détail lors de la génération du message d'erreur.
add_constructor
dans votre __init__
méthode.
Mise à jour : la bibliothèque est obsolète au profit 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éé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)
yodl
sur github.
Sur mon installation For PyYaml pour Python 2.7, j'ai mis à jour __init__.py, constructor.py et loader.py. Prend désormais en charge l'option object_pairs_hook pour les commandes de chargement. La différence 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)
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])