web-dev-qa-db-fra.com

PyYAML peut-il transférer des éléments de dict dans un ordre non alphabétique?

J'utilise yaml.dump pour générer un dict. Il imprime chaque article dans l'ordre alphabétique en fonction de la clé.

>>> d = {"z":0,"y":0,"x":0}
>>> yaml.dump( d, default_flow_style=False )
'x: 0\ny: 0\nz: 0\n'

Existe-t-il un moyen de contrôler l’ordre des paires clé/valeur?

Dans mon cas d'utilisation particulier, l'impression inversée serait (par coïncidence) suffisante. Pour être complet, je cherche une réponse qui montre comment contrôler l’ordre plus précisément.

J'ai envisagé d'utiliser collections.OrderedDict mais PyYAML ne semble pas le supporter. J'ai également examiné le sous-classement yaml.Dumper, mais je n'ai pas été en mesure de déterminer s'il peut modifier l'ordre des articles.

26
mwcz

Il existe probablement une meilleure solution de contournement, mais je n'ai rien trouvé dans la documentation ni dans la source. 


Python 2 (voir commentaires)

J'ai sous-classé OrderedDict et lui ai renvoyé une liste d'éléments non triables:

from collections import OrderedDict

class UnsortableList(list):
    def sort(self, *args, **kwargs):
        pass

class UnsortableOrderedDict(OrderedDict):
    def items(self, *args, **kwargs):
        return UnsortableList(OrderedDict.items(self, *args, **kwargs))

yaml.add_representer(UnsortableOrderedDict, yaml.representer.SafeRepresenter.represent_dict)

Et cela semble fonctionner:

>>> d = UnsortableOrderedDict([
...     ('z', 0),
...     ('y', 0),
...     ('x', 0)
... ])
>>> yaml.dump(d, default_flow_style=False)
'z: 0\ny: 0\nx: 0\n'

Python 3 ou 2 (voir commentaires)

Vous pouvez également écrire un représentant personnalisé, mais je ne sais pas si vous rencontrerez des problèmes plus tard, car j'ai enlevé du code de vérification de style:

import yaml

from collections import OrderedDict

def represent_ordereddict(dumper, data):
    value = []

    for item_key, item_value in data.items():
        node_key = dumper.represent_data(item_key)
        node_value = dumper.represent_data(item_value)

        value.append((node_key, node_value))

    return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', value)

yaml.add_representer(OrderedDict, represent_ordereddict)

Mais avec cela, vous pouvez utiliser la classe native OrderedDict.

39
Blender

Si vous mettez à niveau PyYAML vers la version 5.1, il prend désormais en charge le dump sans trier les clés de la manière suivante:

yaml.dump(data, default_flow_style=False, sort_keys=False)

c’est très nouveau, il vient d’être corrigé il ya quelques heures lorsque je tape.

27
Cooper.Wu

Un seul paquebot pour les gouverner tous:

yaml.add_representer(dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))

C'est tout. Finalement. Après toutes ces années et ces heures, le puissant represent_dict a été mis en échec en lui donnant le dict.items() au lieu de dict

Voici comment cela fonctionne:

Voici le code source PyYaml pertinent:

    if hasattr(mapping, 'items'):
        mapping = list(mapping.items())
        try:
            mapping = sorted(mapping)
        except TypeError:
            pass
    for item_key, item_value in mapping:

Pour éviter le tri, nous avons simplement besoin d'un objet Iterable[Pair] qui n'a pas .items().

dict_items est un candidat idéal pour cela.

Voici comment faire cela sans affecter l'état global du module yaml:

#Using a custom Dumper class to prevent changing the global state
class CustomDumper(yaml.Dumper):
    #Super neat hack to preserve the mapping key order. See https://stackoverflow.com/a/52621703/1497385
    def represent_dict_preserve_order(self, data):
        return self.represent_dict(data.items())    

CustomDumper.add_representer(dict, CustomDumper.represent_dict_preserve_order)

return yaml.dump(component_dict, Dumper=CustomDumper)
6
Ark-kun

Ceci est vraiment juste un addendum à la réponse de @ Blender. Si vous regardez dans la source PyYAML, dans le module representer.py, vous trouvez cette méthode:

def represent_mapping(self, tag, mapping, flow_style=None):
    value = []
    node = MappingNode(tag, value, flow_style=flow_style)
    if self.alias_key is not None:
        self.represented_objects[self.alias_key] = node
    best_style = True
    if hasattr(mapping, 'items'):
        mapping = mapping.items()
        mapping.sort()
    for item_key, item_value in mapping:
        node_key = self.represent_data(item_key)
        node_value = self.represent_data(item_value)
        if not (isinstance(node_key, ScalarNode) and not node_key.style):
            best_style = False
        if not (isinstance(node_value, ScalarNode) and not node_value.style):
            best_style = False
        value.append((node_key, node_value))
    if flow_style is None:
        if self.default_flow_style is not None:
            node.flow_style = self.default_flow_style
        else:
            node.flow_style = best_style
    return node

Si vous supprimez simplement la ligne mapping.sort(), elle conserve l'ordre des éléments dans la variable OrderedDict

Une autre solution est donnée dans ce post . Cela ressemble à @ Blender, mais fonctionne pour safe_dump. L'élément commun est la conversion du dict en une liste de n-uplets. Le contrôle if hasattr(mapping, 'items') est évalué à false. 

Mettre à jour: 

Je viens de remarquer que le référentiel EPEL du projet Fedora contient un package appelé python2-yamlordereddictloader, et il en existe un pour Python 3 également. Le projet en amont pour ce package est probablement multiplate-forme. 

3
orodbhen

Il y a deux choses que vous devez faire pour obtenir ceci comme vous voulez: 

  • vous devez utiliser autre chose qu'une dict, car elle ne garde pas les articles commandés
  • vous devez vider cette alternative de la manière appropriée.¹

import sys
import ruamel.yaml
from ruamel.yaml.comments import CommentedMap

d = CommentedMap()
d['z'] = 0
d['y'] = 0
d['x'] = 0

ruamel.yaml.round_trip_dump(d, sys.stdout)

sortie:

z: 0
y: 0
x: 0

¹ Cela a été fait en utilisant ruamel.yaml un analyseur syntaxique YAML 1.2, dont je suis l'auteur.

2
Anthon

Pour Python 3.7+, les dictés préservent l'ordre d'insertion. Il est préférable d'utiliser une bibliothèque respectant cela, telle que oyaml :

>>> import oyaml as yaml  # pip install oyaml
>>> d = {"z": 0, "y": 0, "x": 0}
>>> yaml.dump(d, default_flow_style=False)
'z: 0\ny: 0\nx: 0\n'
1
wim

Si safe_dump (c'est-à-dire dump avec _Dumper=SafeDumper_) est utilisé, l'appel de _yaml.add_representer_ n'a aucun effet. Dans ce cas, il est nécessaire d'appeler explicitement la méthode _add_representer_ sur SafeRepresenter class:

_yaml.representer.SafeRepresenter.add_representer(
    OrderedDict, ordered_dict_representer
)
_
1
Peter Bašista