Le bon outil pour le travail – Real Python

By | mars 29, 2021

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 et dict
  • Comprendre le avantages et les inconvénients d'utilisation OrdonnéDict contre dict

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.

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 le lru_cache qui modifie fréquemment l'ordre sans toucher au sous-jacent dict. Intentionnellement, le Ordonné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 un move_to_end () et un popitem () qui apparaît efficacement de chaque extrémité). Le Ordonné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:

  1. Signalisation d'intention: Si tu utilises OrdonnéDict plus de dict, 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.
  2. 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 ().
  3. 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 son dict.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:

  1. clé contient la clé qui identifie l'élément que vous souhaitez déplacer. Si clé n'existe pas, alors vous obtenez un KeyError.

  2. 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éfaut Vrai, 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, =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, =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, =4)
>>> lettres_1 = OrdonnéDict(b=2, une=1, c=3, =4)
>>> lettres_2 = OrdonnéDict(une=1, b=2, c=3, =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, =4)
>>> lettres_1 = dict(b=2, une=1, c=3, =4)
>>> lettres_2 = dict(une=1, b=2, c=3, =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, =4)
>>> lettres_1 = dict(b=2, une=1, c=3, =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, =4, une=1, c=3)
>>> des lettres.__dict__


>>> lettres1 = dict(b=2, =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, =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 ().

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, =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 tryexcept 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 et dict
  • What the pros et cons are of using OrderedDict contre dict

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]