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?

99
demandé sur dreftymac 2011-02-25 22:52:12

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)
122
répondu coldfix 2018-10-04 16:09:40

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)
52
répondu Brice M. Dempsey 2016-03-01 07:21:04

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.

18
répondu wim 2018-02-21 08:06:29

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é!)

18
répondu Anthon 2018-08-07 16:33:19

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
14
répondu Eric Naeseth 2018-05-25 15:54:35

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)
9
répondu Alex Chekunkov 2018-05-25 17:34:00

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)
3
répondu EricGreg 2013-08-25 21:48:01

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 ).

1
répondu Ilia K. 2011-10-25 13:08:34

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])
0
répondu Adam Murphy 2015-07-07 13:08:50