web-dev-qa-db-fra.com

Y a-t-il des raisons de ne pas utiliser un OrderedDict?

Je fais référence au OrderedDict du module collections, qui est un dictionnaire ordonné.

S'il a la fonctionnalité supplémentaire d'être commandable, ce qui, je le réalise, n'est souvent pas nécessaire, mais malgré tout, y a-t-il des inconvénients? Est-ce plus lent? Manque-t-il une fonctionnalité? Je n'ai vu aucune méthode manquante.

En bref, pourquoi ne devrait pas je l'utilise toujours au lieu d'un dictionnaire normal?

62
temporary_user_name

OrderedDict est une sous-classe de dict, et a besoin de plus de mémoire pour garder une trace de l'ordre dans lequel les clés sont ajoutées. Ce n'est pas anodin. L'implémentation ajoute un second dict sous les couvertures, et une liste doublement liée de toutes les clés (c'est la partie qui se souvient de l'ordre), et un tas de proxys faibles. Ce n'est pas un lot plus lent, mais au moins double la mémoire en utilisant un simple dict.

Mais si c'est approprié, utilisez-le! Voilà pourquoi il est là :-)

Comment ça fonctionne

Le dict de base est juste un dict ordinaire mappant les clés aux valeurs - il n'est pas du tout "ordonné". Lorsqu'une paire <key, value> Est ajoutée, le key est ajouté à une liste. La liste est la partie qui se souvient de la commande.

Mais s'il s'agissait d'une Python, suppression une clé prendrait O(n) fois deux fois: O(n) temps pour trouver la clé dans la liste, et O(n) temps pour supprimer la clé de la liste.

C'est donc plutôt une liste doublement liée. Cela rend la suppression d'une constante constante (O(1)). Mais nous devons encore trouver le nœud de liste doublement lié appartenant à la clé. Pour que cette opération O(1) soit également temps, un second dict - caché - mappe les clés aux nœuds dans la liste doublement liée.

Ainsi, l'ajout d'une nouvelle paire <key, value> Nécessite l'ajout de la paire au dict de base, la création d'un nouveau nœud de liste à double liaison pour contenir la clé, l'ajout de ce nouveau nœud à la liste à double liaison et le mappage de la clé à celle-ci. nouveau nœud dans le dict caché. Un peu plus de deux fois plus de travail, mais toujours O(1) (cas attendu) dans l'ensemble.

De même, la suppression d'une clé présente représente également un peu plus de deux fois plus de travail, mais O(1) le temps global prévu: utilisez le dict caché pour trouver le nœud de liste doublement lié de la clé, supprimez ce nœud de la liste et retirer la clé des deux dict.

Etc. C'est assez efficace.

126
Tim Peters

multithreading

si votre dictionnaire est accessible à partir de plusieurs threads sans verrou, en particulier en tant que point de synchronisation.

Les opérations de Vanilla dict sont atomiques, et tout type étendu en Python ne l'est pas.

En fait, je ne suis même pas certain que OrderedDict est thread-safe (sans verrou), bien que je ne puisse pas ignorer la possibilité qu'il ait été très soigneusement codé et qu'il réponde à la définition de réentrance.

petits démons

utilisation de la mémoire si vous créez des tonnes de ces dictionnaires

utilisation de l'unité centrale de traitement si tout votre code fait est de munir ces dictionnaires

7
Dima Tisnek

pourquoi ne devrais-je pas toujours utiliser cela au lieu d'un dictionnaire normal

Dans Python 2.7, l'utilisation normale de OrderedDict créera des cycles de référence . Donc, toute utilisation de OrderedDict nécessite que le garbage collector soit activé afin de libérer de la mémoire. Oui, le garbage collector est activé par défaut en cPython, mais le désactivant a ses utilisations .

par exemple. Avec cPython 2.7.14

from __future__ import print_function

import collections
import gc

if __== '__main__':
    d = collections.OrderedDict([('key', 'val')])
    gc.collect()
    del d
    gc.set_debug(gc.DEBUG_LEAK)
    gc.collect()
    for i, obj in enumerate(gc.garbage):
        print(i, obj)

les sorties

gc: collectable <list 00000000033E7908>
gc: collectable <list 000000000331EC88>
0 [[[...], [...], 'key'], [[...], [...], 'key'], None]
1 [[[...], [...], None], [[...], [...], None], 'key']

Même si vous créez simplement un OrderedDict (d = collections.OrderedDict()) vide et que vous n'y ajoutez rien, ou si vous essayez explicitement de le nettoyer en appelant la méthode clear ( d.clear() avant del d), vous obtiendrez toujours une liste d'auto-référencement:

gc: collectable <list 0000000003ABBA08>
0 [[...], [...], None]

Cela semble avoir été le cas depuis cette validation a supprimé la méthode __del__ Afin d'empêcher le potentiel de OrderedDict de provoquer des cycles irrécupérables, qui sont sans doute pires. Comme indiqué dans le journal des modifications pour ce commit:

Problème # 9825 : supprimé __del__ de la définition des collections.OrderedDict. Cela empêche les dictionnaires ordonnés auto-référencés créés par l'utilisateur de devenir des ordures GC irrécupérables en permanence. L'inconvénient est que la suppression de __del__ signifie que la liste interne doublement liée doit attendre la collecte GC plutôt que de libérer de la mémoire immédiatement lorsque le refcnt tombe à zéro.


Notez que dans Python 3, le correctif pour le même problème a été fait différemment et utilise des proxys faibles pour éviter les cycles:

Problème # 9825: L'utilisation de __del__ dans la définition des collections.OrderedDict a permis à l'utilisateur de créer des dictionnaires ordonnés auto-référencés qui deviennent des ordures GC définitivement irrécupérables. Rétablissement de l'approche Py3.1 consistant à utiliser des proxys de référence faible afin que les cycles de référence ne soient jamais créés en premier lieu.

3
Day

Depuis Python 3.7, tous les dictionnaires sont garantis pour être ordonnés. Les contributeurs Python ont déterminé que le passage à la commande de dict ordonné n'aurait pas de résultat négatif). impact sur les performances. Je ne sais pas comment les performances de OrderedDict se comparent à dict dans Python> = 3.7, mais j'imagine qu'elles seraient comparables car elles sont tous deux commandés.

Voir également:

1
Flimm