113 votes

Est-ce que PyYAML peut extraire des éléments de dict dans un ordre non-alphabétique ?

J'utilise yaml.dump pour produire un dict. Il imprime chaque élément 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 en sens inverse serait (par coïncidence) suffisante. Cependant, 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 le supporte pas (apparemment). J'ai également envisagé de sous-classer yaml.Dumper mais je n'ai pas réussi à savoir s'il était possible de modifier l'ordre des articles.

246voto

Cooper.Wu Points 557

Si vous mettez à jour PyYAML vers la version 5.1, il supporte maintenant le dump sans trier les clés comme ceci :

yaml.dump(data, sort_keys=False)

Comme indiqué dans help(yaml.Dumper) , sort_keys La valeur par défaut est True :

Dumper(stream, default_style=None, default_flow_style=False,
  canonical=None, indent=None, width=None, allow_unicode=None,
  line_break=None, encoding=None, explicit_start=None, explicit_end=None,
  version=None, tags=None, sort_keys=True)

(Ceux-ci sont transmis en tant que kwargs à la fonction yaml.dump )

49voto

Blender Points 114729

Il y a probablement une meilleure solution, mais je n'ai rien trouvé dans la documentation ou dans les sources.


Python 2 (voir commentaires)

J'ai sous-classé OrderedDict et lui faire renvoyer 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 par la suite, car j'ai supprimé une partie du code de vérification du 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 fonction native OrderedDict classe.

32voto

wim Points 35274

Pour Python 3.7+, les dicts conservent l'ordre d'insertion. Depuis PyYAML 5.1.x, vous pouvez désactiver le tri des clés ( #254 ). Malheureusement, le comportement des clés de tri reste par défaut à True .

>>> import yaml
>>> yaml.dump({"b":1, "a": 2})
'a: 2\nb: 1\n'
>>> yaml.dump({"b":1, "a": 2}, sort_keys=False)
'b: 1\na: 2\n'

Mon projet oyaml est un remplacement monkeypatch/drop-in pour PyYAML. Il préserve l'ordre des dictées par défaut dans toutes les versions de Python et de PyYAML.

>>> import oyaml as yaml  # pip install oyaml
>>> yaml.dump({"b":1, "a": 2})
'b: 1\na: 2\n'

En outre, il videra le collections.OrderedDict comme des mappages normaux, plutôt que comme des objets Python.

>>> from collections import OrderedDict
>>> d = OrderedDict([("b", 1), ("a", 2)])
>>> import yaml
>>> yaml.dump(d)
'!!python/object/apply:collections.OrderedDict\n- - - b\n    - 1\n  - - a\n    - 2\n'
>>> yaml.safe_dump(d)
RepresenterError: ('cannot represent an object', OrderedDict([('b', 1), ('a', 2)]))
>>> import oyaml as yaml
>>> yaml.dump(d)
'b: 1\na: 2\n'
>>> yaml.safe_dump(d)
'b: 1\na: 2\n'

15voto

Ark-kun Points 3098

Un seul mot à retenir :

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

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

Voici comment cela fonctionne :

Voici le code source PyYaml correspondant :

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

Pour empêcher le tri, il suffit de quelques Iterable[Pair] qui n'a pas de .items() .

dict_items est un candidat idéal pour cela.

Voici comment le faire 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)

3voto

orodbhen Points 839

Il s'agit en fait d'un complément à la réponse de @Blender. Si vous regardez dans le PyYAML à la source, au representer.py vous trouverez 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 le mapping.sort() il maintient l'ordre des éléments dans la ligne OrderedDict .

Une autre solution est donnée dans ce poste . Il est similaire à celui de @Blender, mais fonctionne pour safe_dump . L'élément commun est la conversion du dict en une liste de tuples. if hasattr(mapping, 'items') est évaluée à false.

Mise à jour :

Je viens de remarquer que le repo EPEL du projet Fedora contient un paquetage appelé python2-yamlordereddictloader et il en existe un pour Python 3 également. Le projet en amont de ce paquet est probablement multiplateforme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X