web-dev-qa-db-fra.com

En Python, comment pouvez-vous charger des mappages YAML en tant que OrderedDicts?

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?

105
Eric Naeseth

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
15
Eric Naeseth

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)
124
coldfix

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)
52
Brice M. Dempsey

Option 2018:

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.

26
wim

Option 2015 (et ultérieure):

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

19
Anthon

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)
10
Alex Chekunkov

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)
4
EricGreg

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

2
Ilia K.

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
Adam Murphy