Python pas cher
Parfois, vous avez besoin d'un dictionnaire Python qui se souvient de l'ordre de ses éléments. Dans le passé, vous n'aviez qu'un seul outil pour résoudre ce problème spécifique: Python OrdonnéDict
. Il s’agit d’une sous-classe de dictionnaire spécialement conçue pour mémoriser l’ordre des éléments, qui est défini par l’ordre d’insertion des clés.
Cela a changé dans Python 3.6. Le intégré dict
class garde désormais également ses éléments en ordre. Pour cette raison, de nombreux membres de la communauté Python se demandent maintenant si OrdonnéDict
est toujours utile. Un regard plus attentif sur OrdonnéDict
découvrira que cette classe fournit toujours des fonctionnalités précieuses.
Dans ce didacticiel, vous apprendrez à:
- Créer et utiliser
OrdonnéDict
objets dans votre code - Identifiez le différences compris entre
OrdonnéDict
etdict
- Comprendre le avantages et les inconvénients d'utilisation
OrdonnéDict
contredict
Grâce à ces connaissances, vous pourrez choisir la classe de dictionnaire qui correspond le mieux à vos besoins lorsque vous souhaitez conserver l'ordre des éléments.
À la fin du didacticiel, vous verrez un exemple de mise en œuvre d'une file d'attente basée sur un dictionnaire à l'aide de OrdonnéDict
, ce qui serait plus difficile si vous utilisiez un dict
objet.
Choisir entre OrdonnéDict
et dict
Pendant des années, les dictionnaires Python étaient des structures de données non ordonnées. Les développeurs Python étaient habitués à ce fait et s'appuyaient sur des listes ou d'autres séquences lorsqu'ils avaient besoin de garder leurs données en ordre. Avec le temps, les développeurs ont trouvé un besoin pour un nouveau type de dictionnaire, un qui garderait ses éléments en ordre.
En 2008, PEP 372 a introduit l'idée d'ajouter une nouvelle classe de dictionnaire à collections
. Son objectif principal était de se souvenir de l'ordre des éléments tel que défini par l'ordre dans lequel les clés étaient insérées. C'était l'origine de OrdonnéDict
.
Les développeurs principaux de Python voulaient combler le vide et fournir un dictionnaire qui pourrait préserver l'ordre des clés insérées. Cela, à son tour, a permis une implémentation plus simple d'algorithmes spécifiques qui reposent sur cette propriété.
OrdonnéDict
a été ajouté à la bibliothèque standard de Python 3.1. Son API est essentiellement la même que dict
. Pourtant, OrdonnéDict
itère sur les clés et les valeurs dans le même ordre que les clés ont été insérées. Si une nouvelle entrée remplace une entrée existante, l'ordre des éléments reste inchangé. Si une entrée est supprimée et réinsérée, elle sera déplacée à la fin du dictionnaire.
Python 3.6 a introduit une nouvelle implémentation de dict
. Cette nouvelle implémentation représente une grande victoire en termes d'utilisation de la mémoire et d'efficacité d'itération. De plus, la nouvelle implémentation fournit une fonctionnalité nouvelle et quelque peu inattendue: dict
les objets conservent désormais leurs éléments dans le même ordre qu'ils ont été introduits. Au départ, cette fonctionnalité était considérée comme un détail d'implémentation, et la documentation déconseillait de s'y fier.
Noter: Dans ce didacticiel, vous vous concentrerez sur les implémentations de dict
et OrdonnéDict
que CPython fournit.
Dans les paroles de Raymond Hettinger, développeur principal de Python et co-auteur de OrdonnéDict
, la classe a été spécialement conçue pour garder ses articles en ordre, alors que la nouvelle implémentation de dict
a été conçu pour être compact et pour fournir une itération rapide:
Le dictionnaire régulier actuel est basé sur le design que j'ai proposé il y a plusieurs années. Les principaux objectifs de cette conception étaient la compacité et une itération plus rapide sur les tableaux denses de clés et de valeurs. Le maintien de l'ordre était un artefact plutôt qu'un objectif de conception. Le design permet de maintenir l'ordre mais ce n'est pas sa spécialité.
En revanche, j'ai donné
collections.OrderedDict
un design différent (plus tard codé en C par Eric Snow). L'objectif principal était d'avoir un maintien efficace de l'ordre, même pour des charges de travail sévères telles que celles imposées par lelru_cache
qui modifie fréquemment l'ordre sans toucher au sous-jacentdict
. Intentionnellement, leOrdonnéDict
a une conception qui donne la priorité aux capacités de commande au détriment d'une surcharge de mémoire supplémentaire et d'un facteur constant de pire temps d'insertion.C'est toujours mon objectif d'avoir
collections.OrderedDict
ont un design différent avec des caractéristiques de performance différentes de celles des dicts ordinaires. Il a des méthodes spécifiques à l'ordre que les dictionnaires ordinaires n'ont pas (comme unmove_to_end ()
et unpopitem ()
qui apparaît efficacement de chaque extrémité). LeOrdonnéDict
doit être bon dans ces opérations parce que c'est ce qui le différencie des dictionnaires réguliers. (La source)
Dans Python 3.7, la fonction de tri des éléments de dict
objets a été déclaré une partie officielle de la spécification du langage Python. Ainsi, à partir de ce moment, les développeurs pourront compter sur dict
quand ils avaient besoin d'un dictionnaire qui garde ses éléments en ordre.
À ce stade, une question se pose: Est-ce que OrdonnéDict
encore nécessaire après cette nouvelle implémentation de dict
? La réponse dépend de votre cas d'utilisation spécifique et également de la manière dont vous souhaitez être explicite dans votre code.
Au moment de la rédaction de cet article, certaines caractéristiques de OrdonnéDict
le rendait encore précieux et différent d'un régulier dict
:
- Signalisation d'intention: Si tu utilises
OrdonnéDict
plus dedict
, votre code indique clairement que l'ordre des éléments dans le dictionnaire est important. Vous indiquez clairement que votre code a besoin ou dépend de l'ordre des éléments dans le dictionnaire sous-jacent. - Contrôle de l'ordre des articles: Si vous devez réorganiser ou réorganiser les éléments dans un dictionnaire, vous pouvez utiliser
.move_to_end ()
et aussi la variation améliorée de.popitem ()
. - Comportement du test d'égalité: Si votre code compare les dictionnaires pour l'égalité et que l'ordre des éléments est important dans cette comparaison, alors
OrdonnéDict
est le bon choix.
Il y a au moins une autre raison de continuer à utiliser OrdonnéDict
dans votre code: rétrocompatibilité. S'appuyer régulièrement dict
Les objets pour préserver l'ordre des éléments briseront votre code dans les environnements qui exécutent des versions de Python antérieures à la version 3.6.
Il est difficile de dire si dict
remplacera entièrement OrdonnéDict
bientôt. Aujourd'hui, OrdonnéDict
offre toujours des fonctionnalités intéressantes et précieuses que vous voudrez peut-être prendre en compte lors de la sélection d'un outil pour un travail donné.
Premiers pas avec Python OrdonnéDict
Python OrdonnéDict
est un dict
sous-classe qui préserve l'ordre dans lequel paires clé-valeur, mieux connu sous le nom de éléments, sont insérés dans le dictionnaire. Lorsque vous parcourez un OrdonnéDict
objet, les éléments sont parcourus dans l'ordre d'origine. Si vous mettez à jour la valeur d'une clé existante, l'ordre reste inchangé. Si vous supprimez un élément et le réinsérez, l'élément est ajouté à la fin du dictionnaire.
Être un dict
sous-classe signifie qu'elle hérite de toutes les méthodes fournies par un dictionnaire ordinaire. OrdonnéDict
propose également des fonctionnalités supplémentaires que vous découvrirez dans ce didacticiel. Dans cette section, cependant, vous apprendrez les bases de la création et de l'utilisation OrdonnéDict
objets dans votre code.
Création OrdonnéDict
Objets
contrairement à dict
, OrdonnéDict
n'est pas un type intégré, donc la première étape pour créer OrdonnéDict
objets consiste à importer la classe à partir de collections
. Il existe plusieurs façons de créer des dictionnaires ordonnés. La plupart d'entre eux sont identiques à la façon dont vous créez un dict
objet. Par exemple, vous pouvez créer un vide OrdonnéDict
object en instanciant la classe sans arguments:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict()
>>> Nombres[[[["une"] = 1
>>> Nombres[[[["deux"] = 2
>>> Nombres[[[["Trois"] = 3
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
Dans ce cas, vous importez d'abord OrdonnéDict
de collections
. Ensuite, vous créez un dictionnaire ordonné vide en instanciant OrdonnéDict
sans fournir d'arguments au constructeur.
Vous pouvez ajouter des paires clé-valeur au dictionnaire en fournissant une clé entre crochets ([]
) et attribuer une valeur à cette clé. Lorsque vous référencez Nombres
, vous obtenez un itérable de paires clé-valeur qui contient les éléments dans le même ordre où ils ont été insérés dans le dictionnaire.
Vous pouvez également passer un élément itérable d'éléments comme argument au constructeur de OrdonnéDict
:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict([([([([("une", 1), ("deux", 2), ("Trois", 3)])
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
>>> des lettres = OrdonnéDict(("une", 1), ("b", 2), ("c", 3))
>>> des lettres
OrderedDict ([('c', 3), ('a', 1), ('b', 2)])
Lorsque vous utilisez une séquence, telle qu'un liste
ou un tuple
, l'ordre des éléments dans le dictionnaire ordonné résultant correspond à l'ordre d'origine des éléments dans la séquence d'entrée. Si vous utilisez un ensemble
, comme dans le deuxième exemple ci-dessus, l'ordre final des éléments est inconnu jusqu'à ce que le OrdonnéDict
est créé.
Si vous utilisez un dictionnaire normal comme initialiseur pour un OrdonnéDict
objet et que vous utilisez Python 3.6 ou une version ultérieure, vous obtenez le comportement suivant:
Python 3.9.0 (par défaut, 5 octobre 2020, 17:52:02)
[GCC 9.3.0] sur linux
Tapez "aide", "copyright", "crédits" ou "licence" pour plus d'informations.
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict("une": 1, "deux": 2, "Trois": 3)
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
L'ordre des éléments dans le OrdonnéDict
objet correspond à l'ordre dans le dictionnaire d'origine. En revanche, si vous utilisez une version Python inférieure à 3.6, l'ordre des éléments est inconnu:
Python 3.5.10 (par défaut, 25 janvier 2021, 13:22:52)
[GCC 9.3.0] sur linux
Tapez "aide", "copyright", "crédits" ou "licence" pour plus d'informations.
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict("une": 1, "deux": 2, "Trois": 3)
>>> Nombres
OrderedDict ([('one', 1), ('three', 3), ('two', 2)])
Étant donné que les dictionnaires de Python 3.5 ne se souviennent pas de l’ordre de leurs éléments, vous ne connaissez pas l’ordre dans le dictionnaire ordonné résultant tant que l’objet n’a pas été créé. À partir de ce moment, l'ordre est maintenu.
Vous pouvez créer un dictionnaire ordonné en passant des arguments de mot-clé au constructeur de classe:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
Depuis Python 3.6, les fonctions conservent l'ordre des arguments de mot-clé passés dans un appel. Donc, l'ordre des éléments ci-dessus OrdonnéDict
correspond à l'ordre dans lequel vous passez les arguments de mot-clé au constructeur. Dans les versions antérieures de Python, cet ordre est inconnu.
Pour terminer, OrdonnéDict
fournit également .fromkeys ()
, qui crée un nouveau dictionnaire à partir d'un itérable de clés et définit toutes ses valeurs sur une valeur commune:
>>> de collections importer OrdonnéDict
>>> clés = [[[["une", "deux", "Trois"]
>>> OrdonnéDict.fromkeys(clés, 0)
OrderedDict ([('one', 0), ('two', 0), ('three', 0)])
Dans ce cas, vous créez un dictionnaire ordonné en utilisant une liste de clés comme point de départ. Le deuxième argument de .fromkeys ()
fournit une valeur unique à tous les éléments du dictionnaire.
Gérer les éléments dans un OrdonnéDict
Puisque OrdonnéDict
est une structure de données modifiable, vous pouvez effectuer opérations de mutation sur ses instances. Vous pouvez insérer de nouveaux éléments, mettre à jour et supprimer des éléments existants, etc. Si vous insérez un nouvel élément dans un dictionnaire trié existant, l'élément est ajouté à la fin du dictionnaire:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
>>> Nombres[[[["quatre"] = 4
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3), ('four', 4)])
L'élément nouvellement ajouté, ('quatre', 4)
, est placé à la fin du dictionnaire sous-jacent, de sorte que l'ordre des éléments existants reste inchangé et que le dictionnaire conserve l'ordre d'insertion.
Si vous supprimez un élément d'un dictionnaire ordonné existant et insérez à nouveau ce même élément, la nouvelle instance de l'élément est placée à la fin du dictionnaire:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> del Nombres[[[["une"]
>>> Nombres
OrderedDict ([('two', 2), ('three', 3)])
>>> Nombres[[[["une"] = 1
>>> Nombres
OrderedDict ([('two', 2), ('three', 3), ('one', 1)])
Si vous supprimez le ('un 1)
élément et insérez une nouvelle instance du même élément, puis le nouvel élément est ajouté à la fin du dictionnaire sous-jacent.
Si vous réaffectez ou mettez à jour la valeur d'une paire clé-valeur existante dans un OrdonnéDict
objet, la clé conserve sa position mais obtient une nouvelle valeur:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> Nombres[[[["une"] = 1.0
>>> Nombres
OrderedDict ([('one', 1.0), ('two', 2), ('three', 3)])
>>> Nombres.mettre à jour(deux=2,0)
>>> Nombres
OrderedDict ([('one', 1.0), ('two', 2.0), ('three', 3)])
Si vous mettez à jour la valeur d'une clé donnée dans un dictionnaire ordonné, la clé n'est pas déplacée mais la nouvelle valeur est affectée. De la même manière, si vous utilisez .mettre à jour()
pour modifier la valeur d'une paire clé-valeur existante, le dictionnaire se souvient de la position de la clé et lui attribue la valeur mise à jour.
Itérer sur un OrdonnéDict
Tout comme avec les dictionnaires classiques, vous pouvez parcourir un OrdonnéDict
objet en utilisant plusieurs outils et techniques. Vous pouvez parcourir directement les clés ou utiliser des méthodes de dictionnaire, telles que .éléments()
, .clés()
, et .valeurs()
:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> # Itérer directement sur les touches
>>> pour clé dans Nombres:
... impression(clé, "->", Nombres[[[[clé])
...
un -> 1
deux -> 2
trois -> 3
>>> # Itérer sur les éléments en utilisant .items ()
>>> pour clé, évaluer dans Nombres.éléments():
... impression(clé, "->", évaluer)
...
un -> 1
deux -> 2
trois -> 3
>>> # Itérer sur les touches en utilisant .keys ()
>>> pour clé dans Nombres.clés():
... impression(clé, "->", Nombres[[[[clé])
...
un -> 1
deux -> 2
trois -> 3
>>> # Itérer sur les valeurs en utilisant .values ()
>>> pour évaluer dans Nombres.valeurs():
... impression(évaluer)
...
1
2
3
La première pour
boucle itère sur les clés de Nombres
directement. Les trois autres boucles utilisent des méthodes de dictionnaire pour parcourir les éléments, les clés et les valeurs de Nombres
.
Itération dans l'ordre inverse avec renversé()
Une autre caractéristique importante qui OrdonnéDict
a fourni depuis Python 3.5 est que ses éléments, clés et valeurs prennent en charge l'itération inverse en utilisant renversé()
. Cette fonctionnalité a été ajoutée aux dictionnaires réguliers de Python 3.8. Donc, si votre code l'utilise, alors votre compatibilité descendante est beaucoup plus restreinte avec les dictionnaires normaux.
Vous pouvez utiliser renversé()
avec les éléments, les clés et les valeurs d'un OrdonnéDict
objet:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> # Itérer sur les touches directement dans l'ordre inverse
>>> pour clé dans renversé(Nombres):
... impression(clé, "->", Nombres[[[[clé])
...
trois -> 3
deux -> 2
un -> 1
>>> # Parcourez les éléments dans l'ordre inverse
>>> pour clé, évaluer dans renversé(Nombres.éléments()):
... impression(clé, "->", évaluer)
...
trois -> 3
deux -> 2
un -> 1
>>> # Répétez les touches dans l'ordre inverse
>>> pour clé dans renversé(Nombres.clés()):
... impression(clé, "->", Nombres[[[[clé])
...
trois -> 3
deux -> 2
un -> 1
>>> # Itérer les valeurs dans l'ordre inverse
>>> pour évaluer dans renversé(Nombres.valeurs()):
... impression(évaluer)
...
3
2
1
Chaque boucle de cet exemple utilise renversé()
pour parcourir différents éléments d'un dictionnaire ordonné dans l'ordre inverse.
Les dictionnaires réguliers prennent également en charge l'itération inverse. Cependant, si vous essayez d'utiliser renversé()
avec un habitué dict
objet dans une version Python inférieure à 3.8, alors vous obtenez un Erreur-type
:
Python 3.7.9 (par défaut, 14 janvier 2021, 11:41:20)
[GCC 9.3.0] sur linux
Tapez "aide", "copyright", "crédits" ou "licence" pour plus d'informations.
>>> Nombres = dict(une=1, deux=2, Trois=3)
>>> pour clé dans renversé(Nombres):
... impression(clé)
...
Traceback (dernier appel le plus récent):
Déposer "" , ligne 1, dans
Erreur-type: L'objet 'dict' n'est pas réversible
Si vous devez parcourir les éléments d'un dictionnaire dans l'ordre inverse, alors OrdonnéDict
est un bon allié. L'utilisation d'un dictionnaire standard réduit considérablement votre compatibilité descendante car l'itération inverse n'a pas été ajoutée aux dictionnaires standards avant Python 3.8.
Explorer les fonctionnalités uniques de Python OrdonnéDict
Depuis Python 3.6, les dictionnaires réguliers ont conservé leurs éléments dans le même ordre qu'ils ont été insérés dans le dictionnaire sous-jacent. Cela limite l'utilité de OrdonnéDict
, comme vous l’avez vu jusqu’à présent. Pourtant, OrdonnéDict
fournit des fonctionnalités uniques que vous ne pouvez pas trouver dans une version classique dict
objet.
Avec un dictionnaire ordonné, vous avez accès aux méthodes supplémentaires et améliorées suivantes:
-
.move_to_end ()
est une nouvelle méthode ajoutée dans Python 3.2 qui vous permet de déplacer un élément existant vers la fin ou vers le début du dictionnaire. -
.popitem ()
est une variante améliorée de sondict.popitem ()
contrepartie qui vous permet de supprimer et de renvoyer un élément de la fin ou du début du dictionnaire ordonné sous-jacent.
OrdonnéDict
et dict
se comportent également différemment lorsqu'ils sont testés pour l'égalité. Plus précisément, lorsque vous comparez des dictionnaires ordonnés, l'ordre des éléments est important. Ce n’est pas le cas des dictionnaires classiques.
Pour terminer, OrdonnéDict
les instances fournissent un attribut appelé .__ dict__
que vous ne trouvez pas dans une instance de dictionnaire classique. Cet attribut vous permet d'ajouter des attributs inscriptibles personnalisés à un dictionnaire ordonné existant.
Réorganiser les articles avec .move_to_end ()
L'une des différences les plus remarquables entre dict
et OrdonnéDict
est que ce dernier a une méthode supplémentaire appelée .move_to_end ()
. Cette méthode vous permet de déplacer les éléments existants vers la fin ou le début du dictionnaire sous-jacent. C'est donc un excellent outil pour réorganiser un dictionnaire.
Lorsque vous utilisez .move_to_end ()
, vous pouvez fournir deux arguments:
-
clé
contient la clé qui identifie l'élément que vous souhaitez déplacer. Siclé
n'existe pas, alors vous obtenez unKeyError
. -
dernier
contient une valeur booléenne qui définit vers quelle extrémité du dictionnaire vous souhaitez déplacer l'élément à portée de main. Il est par défautVrai
, ce qui signifie que l'élément sera déplacé à la fin, ou à droite, du dictionnaire.Faux
signifie que l'élément sera déplacé vers l'avant ou vers la gauche du dictionnaire commandé.
Voici un exemple d'utilisation .move_to_end ()
avec un clé
argument et en s'appuyant sur la valeur par défaut de dernier
:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
>>> Nombres.move_to_end("une")
>>> Nombres
OrderedDict ([('two', 2), ('three', 3), ('one', 1)])
Quand vous appelez .move_to_end ()
avec un clé
comme argument, vous déplacez la paire clé-valeur disponible à la fin du dictionnaire. C'est pourquoi ('un 1)
est en dernière position maintenant. Notez que le reste des articles restent dans le même ordre d'origine.
Si tu réussis Faux
à dernier
, puis vous déplacez l'élément au début:
>>> Nombres.move_to_end("une", dernier=Faux)
>>> Nombres
OrderedDict ([('one', 1), ('two', 2), ('three', 3)])
Dans ce cas, vous vous déplacez ('un 1)
au début du dictionnaire. Cela fournit une fonctionnalité intéressante et puissante. Par exemple, avec .move_to_end ()
, vous pouvez trier un dictionnaire ordonné par clés:
>>> de collections importer OrdonnéDict
>>> des lettres = OrdonnéDict(b=2, ré=4, une=1, c=3)
>>> pour clé dans trié(des lettres):
... des lettres.move_to_end(clé)
...
>>> des lettres
OrderedDict ([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
Dans cet exemple, vous créez d'abord un dictionnaire ordonné, des lettres
. Le pour
loop parcourt ses clés triées et déplace chaque élément à la fin du dictionnaire. Lorsque la boucle se termine, votre dictionnaire ordonné a ses éléments triés par clés.
Trier le dictionnaire par valeurs serait un exercice intéressant, alors développez le bloc ci-dessous et essayez-le!
Triez le dictionnaire suivant par valeurs:
>>> de collections importer OrdonnéDict
>>> des lettres = OrdonnéDict(une=4, b=3, ré=1, c=2)
Pour vous aider à mettre en œuvre une solution, pensez à utiliser un lambda
fonction.
Vous pouvez développer le bloc ci-dessous pour voir une solution possible.
Vous pouvez utiliser un lambda
fonction pour récupérer la valeur de chaque paire clé-valeur dans des lettres
et utilisez cette fonction comme clé
argument à trié ()
:
>>> pour clé, _ dans trié(des lettres.éléments(), clé=lambda Objet: Objet[[[[1]):
... des lettres.move_to_end(clé)
...
>>> des lettres
OrderedDict ([('d', 1), ('c', 2), ('b', 3), ('a', 4)])
Dans ce code, vous utilisez un lambda
fonction qui renvoie la valeur de chaque paire clé-valeur dans des lettres
. L'appel à trié ()
utilise ceci lambda
fonction pour extraire un clé de comparaison à partir de chaque élément de l'entrée itérable, lettres.items ()
. Ensuite, vous utilisez .move_to_end ()
Trier des lettres
.
Génial! Vous savez maintenant comment réorganiser vos dictionnaires ordonnés en utilisant .move_to_end ()
. Vous êtes prêt à passer à la section suivante.
Suppression d'éléments avec .popitem ()
Une autre caractéristique intéressante de OrdonnéDict
est sa version améliorée de .popitem ()
. Par défaut, .popitem ()
supprime et renvoie un article dans l'ordre LIFO (dernier entré / premier sorti). En d'autres termes, il supprime les éléments de l'extrémité droite du dictionnaire ordonné:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> Nombres.popitem()
('trois', 3)
>>> Nombres.popitem()
('deux', 2)
>>> Nombres.popitem()
('un 1)
>>> Nombres.popitem()
Traceback (dernier appel le plus récent):
Déposer "", ligne 1, dans
Nombres.popitem()
KeyError: 'le dictionnaire est vide'
Ici, vous supprimez tous les éléments de Nombres
utilisant .popitem ()
. Chaque appel à cette méthode supprime un seul élément de la fin du dictionnaire sous-jacent. Si vous appelez .popitem ()
sur un dictionnaire vide, alors vous obtenez un KeyError
. Jusqu'à ce point, .popitem ()
se comporte de la même manière que dans les dictionnaires classiques.
Dans OrdonnéDict
, toutefois, .popitem ()
accepte également un argument booléen appelé dernier
, qui est par défaut Vrai
. Si vous définissez dernier
à Faux
, alors .popitem ()
supprime les éléments dans l'ordre FIFO (premier entré / premier sorti), ce qui signifie qu'il supprime les éléments du début du dictionnaire:
>>> de collections importer OrdonnéDict
>>> Nombres = OrdonnéDict(une=1, deux=2, Trois=3)
>>> Nombres.popitem(dernier=Faux)
('un 1)
>>> Nombres.popitem(dernier=Faux)
('deux', 2)
>>> Nombres.popitem(dernier=Faux)
('trois', 3)
>>> Nombres.popitem(dernier=Faux)
Traceback (dernier appel le plus récent):
Déposer "", ligne 1, dans
Nombres.popitem(dernier=Faux)
KeyError: 'le dictionnaire est vide'
Avec dernier
mis à Vrai
, vous pouvez utiliser .popitem ()
pour supprimer et renvoyer des éléments depuis le début d'un dictionnaire ordonné. Dans cet exemple, le dernier appel à .popitem ()
soulève un KeyError
car le dictionnaire sous-jacent est déjà vide.
Test d'égalité entre les dictionnaires
Lorsque vous en testez deux OrdonnéDict
objets d'égalité dans un contexte booléen, l'ordre des éléments joue un rôle important. Par exemple, si vos dictionnaires commandés contiennent le même ensemble d'éléments, le résultat du test dépend de leur ordre:
>>> de collections importer OrdonnéDict
>>> lettres_0 = OrdonnéDict(une=1, b=2, c=3, ré=4)
>>> lettres_1 = OrdonnéDict(b=2, une=1, c=3, ré=4)
>>> lettres_2 = OrdonnéDict(une=1, b=2, c=3, ré=4)
>>> lettres_0 == lettres_1
Faux
>>> lettres_0 == lettres_2
Vrai
Dans cet exemple, lettres_1
a une légère différence dans l'ordre de ses articles par rapport à lettres_0
et lettres_2
, donc le premier test renvoie Faux
. Au deuxième test, lettres_0
et lettres_2
ont le même ensemble d'éléments, qui sont dans le même ordre, donc le test renvoie Vrai
.
Si vous essayez ce même exemple en utilisant des dictionnaires standards, vous obtiendrez un résultat différent:
>>> lettres_0 = dict(une=1, b=2, c=3, ré=4)
>>> lettres_1 = dict(b=2, une=1, c=3, ré=4)
>>> lettres_2 = dict(une=1, b=2, c=3, ré=4)
>>> lettres_0 == lettres_1
Vrai
>>> lettres_0 == lettres_2
Vrai
>>> lettres_0 == lettres_1 == lettres_2
Vrai
Ici, lorsque vous testez deux dictionnaires réguliers pour l'égalité, vous obtenez Vrai
si les deux dictionnaires ont le même ensemble d'éléments. Dans ce cas, l'ordre des éléments ne change pas le résultat final.
Enfin, des tests d'égalité entre un OrdonnéDict
objet et un dictionnaire ordinaire ne prennent pas en compte l'ordre des éléments:
>>> de collections importer OrdonnéDict
>>> lettres_0 = OrdonnéDict(une=1, b=2, c=3, ré=4)
>>> lettres_1 = dict(b=2, une=1, c=3, ré=4)
>>> lettres_0 == lettres_1
Vrai
Lorsque vous comparez des dictionnaires ordonnés avec des dictionnaires classiques, l’ordre des éléments n’a pas d’importance. Si les deux dictionnaires ont le même ensemble d'éléments, ils se comparent de manière égale, quel que soit l'ordre de leurs éléments.
Ajout de nouveaux attributs à une instance de dictionnaire
OrdonnéDict
les objets ont un .__ dict__
attribut que vous ne trouvez pas dans les objets de dictionnaire classiques. Jetez un œil au code suivant:
>>> de collections importer OrdonnéDict
>>> des lettres = OrdonnéDict(b=2, ré=4, une=1, c=3)
>>> des lettres.__dict__
>>> lettres1 = dict(b=2, ré=4, une=1, c=3)
>>> lettres1.__dict__
Traceback (dernier appel le plus récent):
Déposer "", ligne 1, dans
lettres1.__dict__
AttributeError: L'objet 'dict' n'a pas d'attribut '__dict__'
Dans le premier exemple, vous accédez au .__ dict__
attribut sur le dictionnaire ordonné des lettres
. Python utilise en interne cet attribut pour stocker les attributs d'instance inscriptibles. Le deuxième exemple montre que les objets de dictionnaire normaux n'ont pas de .__ dict__
attribut.
Vous pouvez utiliser le dictionnaire ordonné .__ dict__
attribut pour stocker les attributs d'instance inscriptibles créés dynamiquement. Il y a plusieurs moyens de le faire. Par exemple, vous pouvez utiliser une affectation de style dictionnaire, comme dans ordonné_dict .__ dict__["attr"] = valeur
. Vous pouvez également utiliser la notation par points, comme dans order_dict.attr = valeur
.
Voici un exemple d'utilisation .__ dict__
pour attacher une nouvelle fonction à un dictionnaire ordonné existant:
>>> de collections importer OrdonnéDict
>>> des lettres = OrdonnéDict(b=2, ré=4, une=1, c=3)
>>> des lettres.sorted_keys = lambda: trié(des lettres.clés())
>>> Vars(des lettres)
'sort_keys': <fonction à 0x7fa1e2fe9160>
>>> des lettres.sorted_keys()
['a', 'b', 'c', 'd']
>>> des lettres[[[["e"] = 5
>>> des lettres.sorted_keys()
['a', 'b', 'c', 'd', 'e']
Maintenant vous avez un .sorted_keys ()
lambda
fonction attachée à votre des lettres
dictionnaire ordonné. Notez que vous pouvez inspecter le contenu de .__ dict__
soit en y accédant directement avec le notation par points ou en utilisant vars ()
.
Noter: Ce type d'attribut dynamique est ajouté à une instance particulière d'une classe donnée. Dans l'exemple ci-dessus, cette instance est des lettres
. Cela n'affecte ni les autres instances ni la classe elle-même, vous n'avez donc accès qu'à .sorted_keys ()
par des lettres
.
You can use this dynamically added function to iterate through the dictionary keys in sorted order without altering the original order in des lettres
:
>>> pour key dans des lettres.sorted_keys():
... impression(key, "->", des lettres[[[[key])
...
a -> 1
b -> 2
c -> 3
d -> 4
e -> 5
>>> des lettres
OrderedDict([('b', 2), ('d', 4), ('a', 1), ('c', 3), ('e', 5)])
This is just an example of how useful this feature of OrderedDict
can be. Note that you can’t do something similar with a regular dictionary:
>>> des lettres = dict(b=2, ré=4, une=1, c=3)
>>> des lettres.sorted_keys = lambda: sorted(des lettres.keys())
Traceback (most recent call last):
File "", line 1, dans
des lettres.sorted_keys = lambda: sorted(des lettres.keys())
AttributeError: 'dict' object has no attribute 'sort_keys'
If you try to dynamically add custom instance attributes to a regular dictionary, then you get an AttributeError
telling you that the underlying dictionary doesn’t have the attribute at hand. That’s because regular dictionaries don’t have a .__dict__
attribute to hold new instance attributes.
Merging and Updating Dictionaries With Operators
Python 3.9 added two new operators to the dictionary space. Now you have merge (|
) et update (|=
) dictionary operators. These operators also work with OrderedDict
instances:
Python 3.9.0 (default, Oct 5 2020, 17:52:02)
[GCC 9.3.0] on linux
Tapez "aide", "copyright", "crédits" ou "licence" pour plus d'informations.
>>> from collections import OrderedDict
>>> physicists = OrderedDict(newton="1642-1726", einstein="1879-1955")
>>> biologists = OrderedDict(darwin="1809-1882", mendel="1822-1884")
>>> scientists = physicists | biologists
>>> scientists
OrderedDict([[[[
('newton', '1642-1726'),
('einstein', '1879-1955'),
('darwin', '1809-1882'),
('mendel', '1822-1884')
])
As its name suggests, the merge operator merges the two dictionaries into a new one that contains the items of both initial dictionaries. If the dictionaries in the expression have common keys, then the rightmost dictionary’s values will prevail.
The update operator is handy when you have a dictionary and want to update some of its values without calling .update()
:
>>> physicists = OrderedDict(newton="1642-1726", einstein="1879-1955")
>>> physicists_1 = OrderedDict(newton="1642-1726/1727", hawking="1942-2018")
>>> physicists |= physicists_1
>>> physicists
OrderedDict([[[[
('newton', '1642-1726/1727'),
('einstein', '1879-1955'),
('hawking', '1942-2018')
])
In this example, you use the dictionary update operator to update Newton’s lifetime information. The operator updates a dictionary in place. If the dictionary that provides the updated data has new keys, then those keys are added to the end of the original dictionary.
Considering Performance
Performance is an important subject in programming. Knowing how fast an algorithm runs or how much memory it uses are common concerns. OrderedDict
was initially coded in Python and then written in C to maximize efficiency in its methods and operations. These two implementations are currently available in the standard library. However, the Python implementation serves as an alternative if the C implementation isn’t available for some reason.
Both implementations of OrderedDict
involve using a doubly linked list to capture the order of items. Using a doubly linked list to keep keys ordered is fast for all operations except for .__delitem__()
, which becomes an O(n) operation.
The rest of the operations on an ordered dictionary are O(1) but with a greater constant factor for .__setitem__()
compared to regular dictionaries. This means that every use case of OrderedDict
suffers this performance impact because most operations use .__setitem__()
.
In general, OrderedDict
has lower performance than regular dictionaries. Here’s an example that measures the execution time of several operations on both dictionary classes:
# time_testing.py
from collections import OrderedDict
from temps import perf_counter
def average_time(dictionary):
time_measurements = []
pour _ dans range(1_000_000):
start = perf_counter()
dictionary[[[["key"] = "value"
"key" dans dictionary
"missing_key" dans dictionary
dictionary[[[["key"]
del dictionary[[[["key"]
finir = perf_counter()
time_measurements.append(finir - start)
revenir sum(time_measurements) / len(time_measurements) * int(1e9)
ordereddict_time = average_time(OrderedDict.fromkeys(range(1000)))
dict_time = average_time(dict.fromkeys(range(1000)))
Gain = ordereddict_time / dict_time
impression(F"OrderedDict: ordereddict_time:.2f ns")
impression(F"dict: dict_time:.2f ns (Gain:.2fx faster)")
In this script, you compute the average_time()
that it takes to run several common operations on a given dictionary. Le pour
loop uses time.pref_counter()
to measure the execution time of the set of operations. The function returns the average time, in nanoseconds, that it takes to run the selected set of operations.
If you run this script from your command line, then you get an output similar to this:
$ python time_testing.py
OrderedDict: 272.93 ns
dict: 197.88 ns (1.38x faster)
As you see in the output, operations on dict
objects are faster than operations on OrderedDict
objects.
Regarding memory consumption, OrderedDict
instances have to pay a storage cost because of their ordered list of keys. Here’s a script that gives you an idea of this memory cost:
>>> import sys
>>> from collections import OrderedDict
>>> ordereddict_memory = sys.getsizeof(OrderedDict.fromkeys(range(1000)))
>>> dict_memory = sys.getsizeof(dict.fromkeys(range(1000)))
>>> Gain = 100 - dict_memory / ordereddict_memory * 100
>>> impression(F"OrderedDict: ordereddict_memory bytes")
OrderedDict: 85408 bytes
>>> impression(F"dict: dict_memory bytes (Gain:.2f% lower)")
dict: 36960 bytes (56.73% lower)
In this example, you use sys.getsizeof()
to measure the memory footprint in bytes of two dictionary objects. In the output, you can see that the regular dictionary occupies less memory than its OrderedDict
homologue.
Selecting the Right Dictionary for the Job
So far, you’ve learned about the subtle differences between OrderedDict
et dict
. You’ve learned that, even though regular dictionaries have been ordered data structures since Python 3.6, there’s still some value in using OrderedDict
because of a set of useful features that aren’t present in dict
.
Here’s a summary of the more relevant differences and features of both classes that you should consider when you’re deciding which one to use:
Feature | OrderedDict |
dict |
---|---|---|
Key insertion order maintained | Yes (since Python 3.1) | Yes (since Python 3.6) |
Readability and intent signaling regarding the order of items | High | Low |
Control over the order of items | High (.move_to_end() , enhanced .popitem() ) |
Low (removing and reinserting items is required) |
Operations performance | Low | High |
Memory consumption | High | Low |
Equality tests consider the order of items | Oui | Non |
Support for reverse iteration | Yes (since Python 3.5) | Yes (since Python 3.8) |
Ability to append new instance attributes | Yes (.__dict__ attribute) |
Non |
Support for the merge (| ) and update (|= ) dictionary operators |
Yes (since Python 3.9) | Yes (since Python 3.9) |
This table summarizes some of the main differences between OrderedDict
et dict
that you should consider when you need to choose a dictionary class to solve a problem or to implement a specific algorithm. In general, if the order of items in the dictionary is vital or even important for your code to work correctly, then your first look should be toward OrderedDict
.
Building a Dictionary-Based Queue
A use case in which you should consider using an OrderedDict
object rather than a dict
object is when you need to implement a dictionary-based queue. Queues are common and useful data structures that manage their items in a FIFO manner. This means that you push in new items at the end of the queue, and old items pop out from the beginning of the queue.
Typically, queues implement an operation to add an item to their end, which is known as an enqueue operation. Queues also implement an operation to remove items from their beginning, which is known as a dequeue operation.
To create a dictionary-based queue, fire up your code editor or IDE, create a new Python module called queue.py
and add the following code to it:
# queue.py
from collections import OrderedDict
class Queue:
def __init__(self, initial_data=None, /, **kwargs):
self.Les données = OrderedDict()
si initial_data est ne pas None:
self.Les données.update(initial_data)
si kwargs:
self.Les données.update(kwargs)
def enqueue(self, item):
key, évaluer = item
si key dans self.Les données:
self.Les données.move_to_end(key)
self.Les données[[[[key] = évaluer
def dequeue(self):
try:
revenir self.Les données.popitem(last=False)
except KeyError:
impression("Empty queue")
def __len__(self):
revenir len(self.Les données)
def __repr__(self):
revenir F"Queue(self.Les données.items())"
Dans Queue
, you first initialize an instance attribute called .data
. This attribute holds an empty ordered dictionary that you’ll use to store the data. The class initializer takes a first optional argument, initial_data
, that allows you to provide initial data when you instantiate the class. The initializer also takes optional keyword arguments (kwargs
) to allow you to use keyword arguments in the constructor.
Then you code .enqueue()
, which allows you to add key-value pairs to the queue. In this case, you use .move_to_end()
if the key already exists, and you use a normal assignment for new keys. Note that for this method to work, you need to provide a two-item tuple
ou list
with a valid key-value pair.
Le .dequeue()
implementation uses .popitem()
with last
set to False
to remove and return items from the beginning of the underlying ordered dictionary, .data
. In this case, you use a try
… except
block to handle the KeyError
that occurs when you call .popitem()
on an empty dictionary.
The special method .__len__()
provides the required functionality for retrieving the length of the internal ordered dictionary, .data
. Finally, the special method .__repr__()
provides a user-friendly string representation of the queue when you print the data structure to the screen.
Here are some examples of how you can use Queue
:
>>> from file d'attente import Queue
>>> # Create an empty queue
>>> empty_queue = Queue()
>>> empty_queue
Queue(odict_items([]))
>>> # Create a queue with initial data
>>> numbers_queue = Queue([([([([("one", 1), ("two", 2)])
>>> numbers_queue
Queue(odict_items([('one', 1), ('two', 2)]))
>>> # Create a queue with keyword arguments
>>> letters_queue = Queue(une=1, b=2, c=3)
>>> letters_queue
Queue(odict_items([('a', 1), ('b', 2), ('c', 3)]))
>>> # Add items
>>> numbers_queue.enqueue(("three", 3))
>>> numbers_queue
Queue(odict_items([('one', 1), ('two', 2), ('three', 3)]))
>>> # Remove items
>>> numbers_queue.dequeue()
('one', 1)
>>> numbers_queue.dequeue()
('two', 2)
>>> numbers_queue.dequeue()
('three', 3)
>>> numbers_queue.dequeue()
Empty queue
In this code example, you first create three different Queue
objects using different approaches. Then you use .enqueue()
to add a single item to the end of numbers_queue
. Finally, you call .dequeue()
several times to remove all the items in numbers_queue
. Note that the final call to .dequeue()
prints a message to the screen to inform you that the queue is already empty.
Conclusion
For years, Python dictionaries were unordered data structures. This revealed the need for an ordered dictionary that helps in situations where the order of items is important. So Python developers created OrderedDict
, which was specially designed to keep its items ordered.
Python 3.6 introduced a new feature into regular dictionaries. Now they also remember the order of items. With this addition, most Python programmers wonder if they still need to consider using OrderedDict
.
In this tutorial, you learned:
- How to create and use
OrderedDict
objets in your code - What the main differences are between
OrderedDict
etdict
- What the pros et cons are of using
OrderedDict
contredict
Now you’re in a better position to make an educated decision on whether to use dict
ou OrderedDict
if your code needs an ordered dictionary.
In this tutorial, you coded an example of how to implement a dictionary-based queue, which is a use case that shows that OrderedDict
can still be of value in your daily Python coding adventures.
[ad_2]