python pour débutant
Python collections
module fournit un riche ensemble de types de données de conteneur spécialisés soigneusement conçu pour aborder des problèmes de programmation spécifiques d'une manière Pythonique et efficace. Le module fournit également des classes wrapper qui rendent plus sûr la création de classes personnalisées qui se comportent de la même manière que les types intégrés dict
, liste
, et str
.
En savoir plus sur les types de données et les classes dans collections
vous permettra de développer votre boîte à outils de programmation avec un ensemble précieux d'outils fiables et efficaces.
Dans ce didacticiel, vous apprendrez à :
- Écrivez lisible et explicite coder avec
nommétuple
- Construire files d'attente et piles efficaces avec
deque
- Compter objets rapidement avec
Compteur
- Gérer clés de dictionnaire manquantes avec
dict par défaut
- Garantir la Ordre d'insertion de clés avec
CommandéDict
- Gérer plusieurs dictionnaires comme une seule unité avec
ChainMap
Pour mieux comprendre les types de données et les classes dans collections
, vous devez connaître les bases de l'utilisation des types de données intégrés de Python, tels que les listes, les tuples et les dictionnaires. De plus, la dernière partie de l'article nécessite des connaissances de base sur la programmation orientée objet en Python.
Premiers pas avec Python collections
De retour dans Python 2.4, Raymond Hettinger contribué un nouveau module appelé collections
à la bibliothèque standard. L'objectif était de fournir divers types de données de collecte spécialisées pour aborder des problèmes de programmation spécifiques.
À ce moment-là, collections
ne comprenait qu'une seule structure de données, deque
, qui a été spécialement conçu comme une file d'attente à deux extrémités qui prend en charge l'efficacité ajouter et pop opérations à chaque extrémité de la séquence. A partir de ce moment, plusieurs modules de la bibliothèque standard ont profité de deque
pour améliorer les performances de leurs classes et structures. Quelques exemples remarquables sont file d'attente
et enfilage
.
Avec le temps, une poignée de types de données de conteneurs spécialisés ont rempli le module :
Type de données | Version Python | La description |
---|---|---|
deque |
2.4 | Une collection de type séquence qui prend en charge l'ajout et la suppression efficaces d'éléments à chaque extrémité de la séquence |
dict par défaut |
2.5 | Une sous-classe de dictionnaire pour construire des valeurs par défaut pour les clés manquantes et les ajouter automatiquement au dictionnaire |
nommétuple() |
2.6 | Une fonction d'usine pour créer des sous-classes de tuple qui fournit des champs nommés qui permettent d'accéder aux éléments par leur nom tout en gardant la possibilité d'accéder aux éléments par index |
CommandéDict |
2.7, 3.1 | Une sous-classe de dictionnaire qui conserve les paires clé-valeur ordonnées en fonction du moment où les clés sont insérées |
Compteur |
2.7, 3.1 | Une sous-classe de dictionnaire qui prend en charge le comptage pratique d'éléments uniques dans une séquence ou itérable |
ChainMap |
3.3 | Une classe de type dictionnaire qui permet de traiter un certain nombre de mappages comme un seul objet de dictionnaire |
Outre ces types de données spécialisés, collections
fournit également trois classes de base qui facilitent la création de listes personnalisées, de dictionnaires et de chaînes :
Classer | La description |
---|---|
UserDict |
Une classe wrapper autour d'un objet dictionnaire qui facilite le sous-classement dict |
Liste d'utilisateur |
Une classe wrapper autour d'un objet liste qui facilite le sous-classement liste |
Chaîne utilisateur |
Une classe wrapper autour d'un objet chaîne qui facilite le sous-classement chaîne de caractères |
Le besoin de ces classes wrapper a été partiellement éclipsé par la possibilité de sous-classer les types de données intégrés standard correspondants. Cependant, l'utilisation de ces classes est parfois plus sûre et moins sujette aux erreurs que l'utilisation de types de données standard.
Avec cette brève introduction à collections
et les cas d'utilisation spécifiques que les structures de données et les classes de ce module peuvent résoudre, il est temps de les examiner de plus près. Avant cela, il est important de souligner que ce tutoriel est une introduction à collections
dans son ensemble. Dans la plupart des sections suivantes, vous trouverez une boîte d'alerte bleue qui vous guidera vers un article dédié sur la classe ou la fonction à portée de main.
Amélioration de la lisibilité du code : nommétuple()
Python nommétuple()
est une fonction d'usine qui vous permet de créer tuple
sous-classes avec champs nommés. Ces champs vous donnent un accès direct aux valeurs dans un tuple nommé donné en utilisant le notation par points, comme dans obj.attr
.
Le besoin de cette fonctionnalité est apparu car l'utilisation d'index pour accéder aux valeurs d'un tuple régulier est ennuyeuse, difficile à lire et sujette aux erreurs. Cela est particulièrement vrai si le tuple avec lequel vous travaillez contient plusieurs éléments et est construit loin de l'endroit où vous l'utilisez.
Une sous-classe de tuple avec des champs nommés auxquels les développeurs peuvent accéder avec la notation par points semblait être une fonctionnalité souhaitable dans Python 2.6. C'est l'origine de nommétuple()
. Les sous-classes de tuples que vous pouvez créer avec cette fonction sont une grande victoire en lisibilité du code si vous les comparez avec des tuples normaux.
Pour mettre le problème de lisibilité du code en perspective, considérons divmod()
. Cette fonction intégrée prend deux nombres (non complexes) et renvoie un tuple avec le quotient et reste qui résultent de la division entière des valeurs d'entrée :
>>> divmod(12, 5)
(2, 2)
Cela fonctionne bien. Cependant, ce résultat est-il lisible ? Pouvez-vous dire quelle est la signification de chaque nombre dans la sortie ? Heureusement, Python offre un moyen d'améliorer cela. Vous pouvez coder une version personnalisée de divmod()
avec un résultat explicite en utilisant nommétuple
:
>>> de collections importer nommétuple
>>> déf custom_divmod(X, oui):
... DivMod = nommétuple("DivMod", « reste du quotient »)
... revenir DivMod(*divmod(X, oui))
...
>>> résultat = custom_divmod(12, 5)
>>> résultat
DivMod(quotient=2, reste=2)
>>> résultat.quotient
2
>>> résultat.reste
2
Vous connaissez maintenant la signification de chaque valeur dans le résultat. Vous pouvez également accéder à chaque valeur indépendante à l'aide de la notation par points et d'un nom de champ descriptif.
Pour créer une nouvelle sous-classe de tuple en utilisant nommétuple()
, vous avez besoin de deux arguments obligatoires :
nom de type
est le nom de la classe que vous créez. Il doit s'agir d'une chaîne avec un identifiant Python valide.nom_champ
est la liste des noms de champs que vous utiliserez pour accéder aux éléments du tuple résultant. Ça peut être:- Un itérable de chaînes, tel que
["field1", "field2", ..., "fieldN"]
- Une chaîne avec des noms de champs séparés par des espaces, tels que
"champ1 champ2 ... champN"
- Une chaîne avec des noms de champs séparés par des virgules, comme
"champ1, champ2, ..., champN"
- Un itérable de chaînes, tel que
Par exemple, voici différentes manières de créer un exemple 2D Indiquer
avec deux coordonnées (X
et oui
) en utilisant nommétuple()
:
>>> de collections importer nommétuple
>>> # Utiliser une liste de chaînes comme noms de champs
>>> Indiquer = nommétuple("Indiquer", [[[["X", "ou"])
>>> indiquer = Indiquer(2, 4)
>>> indiquer
Point(x=2, y=4)
>>> # Accéder aux coordonnées
>>> indiquer.X
2
>>> indiquer.oui
4
>>> indiquer[[[[0]
2
>>> # Utiliser une expression génératrice comme noms de champs
>>> Indiquer = nommétuple("Indiquer", (domaine pour domaine dans "xy"))
>>> Indiquer(2, 4)
Point(x=2, y=4)
>>> # Utilisez une chaîne avec des noms de champs séparés par des virgules
>>> Indiquer = nommétuple("Indiquer", "x, y")
>>> Indiquer(2, 4)
Point(x=2, y=4)
>>> # Utilisez une chaîne avec des noms de champs séparés par des espaces
>>> Indiquer = nommétuple("Indiquer", "x y")
>>> Indiquer(2, 4)
Point(x=2, y=4)
Dans ces exemples, vous créez d'abord Indiquer
utilisant un liste
des noms de champs. Ensuite, vous instanciez Indiquer
faire un indiquer
objet. Notez que vous pouvez accéder X
et oui
par nom de champ et aussi par index.
Les exemples restants montrent comment créer un tuple nommé équivalent avec une chaîne de noms de champs séparés par des virgules, une expression génératrice et une chaîne de noms de champs séparés par des espaces.
Les tuples nommés fournissent également un tas de fonctionnalités intéressantes qui vous permettent de définir des valeurs par défaut pour vos champs, de créer un dictionnaire à partir d'un tuple nommé donné, de remplacer la valeur d'un champ donné, et plus encore :
>>> de collections importer nommétuple
>>> # Définir les valeurs par défaut pour les champs
>>> Personne = nommétuple("Personne", "nom du travail", valeurs par défaut=[[[["Développeur Python"])
>>> personne = Personne("Jeanne")
>>> personne
Personne(nom='Jane', job='Développeur Python')
>>> # Créer un dictionnaire à partir d'un tuple nommé
>>> personne._asdict()
'name' : 'Jane', 'job' : 'Python Developer'
>>> # Remplacer la valeur d'un champ
>>> personne = personne._remplacer(travail="Développeur web")
>>> personne
Personne(nom='Jane', job='Développeur Web')
Ici, vous créez d'abord un Personne
classe en utilisant nommétuple()
. Cette fois, vous utilisez un argument facultatif appelé valeurs par défaut
qui accepte une séquence de valeurs par défaut pour les champs du tuple. Notez que nommétuple()
applique les valeurs par défaut aux champs les plus à droite.
Dans le deuxième exemple, vous créez un dictionnaire à partir d'un tuple nommé existant en utilisant ._asdict()
. Cette méthode renvoie un nouveau dictionnaire qui utilise les noms de champs comme clés.
Enfin, vous utilisez ._remplacer()
remplacer la valeur d'origine de travail
. Cette méthode ne met pas à jour le tuple en place mais renvoie un nouveau tuple nommé avec la nouvelle valeur stockée dans le champ correspondant. Avez-vous une idée de pourquoi ._remplacer()
renvoie un nouveau tuple nommé ?
Construire des files d'attente et des piles efficaces : deque
Python deque
a été la première structure de données dans collections
. Ce type de données de type séquence est une généralisation de piles et de files d'attente conçues pour prendre en charge une mémoire efficace et rapide ajouter et pop opérations aux deux extrémités de la structure de données.
Noter: Le mot deque
se prononce « deck » et signifie rédouble-etrouvé queeuh.
En Python, les opérations d'ajout et de suppression au début ou à gauche de liste
les objets sont inefficaces, avec O(m) complexité temporelle. Ces opérations sont particulièrement coûteuses si vous travaillez avec de grandes listes car Python doit déplacer tous les éléments vers la droite pour insérer de nouveaux éléments au début de la liste.
D'un autre côté, les opérations d'ajout et de suppression sur le côté droit d'une liste sont normalement efficaces (O(1)) sauf dans les cas où Python a besoin de réaffecter de la mémoire pour augmenter la liste sous-jacente pour accepter de nouveaux éléments.
Python deque
a été créé pour surmonter ce problème. Opérations d'ajout et de suppression des deux côtés d'un deque
object sont stables et tout aussi efficaces car deques sont implémentés sous la forme d'une liste doublement chaînée. C'est pourquoi les deques sont particulièrement utiles pour créer des piles et des files d'attente.
Prenons l'exemple d'une file d'attente. Il gère les éléments dans un Premier entré, premier sorti mode (FIFO). Cela fonctionne comme un tuyau, où vous insérez de nouveaux éléments à une extrémité du tuyau et sortez les anciens éléments de l'autre extrémité. L'ajout d'un élément à la fin d'une file d'attente est appelé file d'attente opération. La suppression d'un élément au début ou au début d'une file d'attente est appelée file d'attente.
Supposons maintenant que vous modélisez une file d'attente de personnes attendant d'acheter des billets pour un film. Vous pouvez le faire avec un deque
. Chaque fois qu'une nouvelle personne arrive, vous la mettez en file d'attente. Lorsque la personne en tête de file obtient ses billets, vous la retirez de la file d'attente.
Voici comment vous pouvez émuler le processus à l'aide d'un deque
objet:
>>> de collections importer deque
>>> ticket_queue = deque()
>>> ticket_queue
deque([])
>>> # Les gens arrivent dans la file d'attente
>>> ticket_queue.ajouter("Jeanne")
>>> ticket_queue.ajouter("John")
>>> ticket_queue.ajouter(" Linda ")
>>> ticket_queue
deque(['Jane', 'John', 'Linda'])
>>> # Les gens ont acheté leurs billets
>>> ticket_queue.popleft()
'Jeanne'
>>> ticket_queue.popleft()
'John'
>>> ticket_queue.popleft()
'Linda'
>>> # Aucune personne dans la file d'attente
>>> ticket_queue.popleft()
Traceback (appel le plus récent en dernier) :
Fichier "" , ligne 1, dans
IndexError: pop d'un deque vide
Ici, vous créez d'abord un vide deque
objet pour représenter la file d'attente des personnes. Pour mettre une personne en file d'attente, vous pouvez utiliser .ajouter()
, qui ajoute des éléments à l'extrémité droite d'un deque. Pour retirer une personne de la file d'attente, vous utilisez .popleft()
, qui supprime et renvoie les éléments à l'extrémité gauche d'un deque.
Noter: Dans la bibliothèque standard Python, vous trouverez file d'attente
. Ce module implémente des files d'attente multi-producteurs et multi-consommateurs utiles pour échanger des informations entre plusieurs threads en toute sécurité.
Le deque
initializer prend deux arguments facultatifs :
itérable
contient un itérable qui sert d'initialiseur.maxlen
contient un nombre entier qui spécifie la longueur maximale dudeque
.
Si vous ne fournissez pas de itérable
, alors vous obtenez un deque vide. Si vous fournissez une valeur à maxlen
, alors votre deque ne stockera que jusqu'à maxlen
éléments.
Avoir un maxlen
est une fonctionnalité pratique. Par exemple, supposons que vous deviez implémenter une liste de fichiers récents dans l'une de vos applications. Dans ce cas, vous pouvez effectuer les opérations suivantes :
>>> de collections importer deque
>>> fichiers récents = deque([[[["core.py", "README.md", "__init__.py"], maxlen=3)
>>> fichiers récents.ajouter à gauche("base de données.py")
>>> fichiers récents
deque(['database.py', 'core.py', 'README.md'], maxlen=3)
>>> fichiers récents.ajouter à gauche("exigences.txt")
>>> fichiers récents
deque(['requirements.txt', 'database.py', 'core.py'], maxlen=3)
Une fois que le deque atteint sa taille maximale (trois fichiers dans ce cas), l'ajout d'un nouveau fichier à une extrémité du deque supprime automatiquement le fichier à l'extrémité opposée. Si vous ne fournissez pas de valeur à maxlen
, le deque peut atteindre un nombre arbitraire d'éléments.
Jusqu'à présent, vous avez appris les bases des deques, y compris comment les créer et comment ajouter et extraire des éléments des deux extrémités d'un deque donné. Deques fournit des fonctionnalités supplémentaires avec une interface de type liste. En voici quelques uns:
>>> de collections importer deque
>>> # Utilisez différents itérables pour créer des demandes
>>> deque((1, 2, 3, 4))
deque([1, 2, 3, 4])
>>> deque([[[[1, 2, 3, 4])
deque([1, 2, 3, 4])
>>> deque("a B c d")
deque(['a', 'b', 'c', 'd'])
>>> # Contrairement aux listes, deque ne prend pas en charge .pop() avec des indices arbitraires
>>> deque("a B c d").pop(2)
Traceback (appel le plus récent en dernier) :
Fichier "" , ligne 1, dans
Erreur-type: pop() ne prend aucun argument (1 donné)
>>> # Prolonger un deque existant
>>> Nombres = deque([[[[1, 2])
>>> Nombres.se déployer([[[[3, 4, 5])
>>> Nombres
deque([1, 2, 3, 4, 5])
>>> Nombres.étendre vers la gauche([[[[-1, -2, -3, -4, -5])
>>> Nombres
deque([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])
>>> # Insérer un élément à une position donnée
>>> Nombres.insérer(5, 0)
>>> Nombres
deque([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
Dans ces exemples, vous créez d'abord des demandes en utilisant différents types d'itérables pour les initialiser. Une différence entre deque
et liste
est-ce deque.pop()
ne prend pas en charge le popping de l'élément à un index donné.
Notez que deque
fournit des méthodes sœurs pour .ajouter()
, .pop()
, et .se déployer()
avec le suffixe la gauche
pour indiquer qu'ils effectuent l'opération correspondante sur l'extrémité gauche du deque sous-jacent.
Deques prend également en charge les opérations de séquence :
Méthode | La description |
---|---|
.dégager() |
Supprimer tous les éléments d'un deque |
.copie() |
Créer une copie superficielle d'un deque |
.count(x) |
Compter le nombre d'éléments deque égal à X |
.remove(valeur) |
Supprimer la première occurrence de valeur |
Une autre caractéristique intéressante des deques est la possibilité de faire pivoter leurs éléments en utilisant .tourner()
:
>>> de collections importer deque
>>> ordinaux = deque([[[["première", "seconde", "la troisième"])
>>> ordinaux.tourner()
>>> ordinaux
deque(['third', 'first', 'second'])
>>> ordinaux.tourner(2)
>>> ordinaux
deque(['first', 'second', 'third'])
>>> ordinaux.tourner(-2)
>>> ordinaux
deque(['third', 'first', 'second'])
>>> ordinaux.tourner(-1)
>>> ordinaux
deque(['first', 'second', 'third'])
Cette méthode fait tourner le deque m
marches vers la droite. La valeur par défaut de m
est 1
. Si vous fournissez une valeur négative à m
, alors la rotation est vers la gauche.
Enfin, vous pouvez utiliser des indices pour accéder aux éléments d'un deque, mais vous ne pouvez pas découper un deque :
>>> de collections importer deque
>>> ordinaux = deque([[[["première", "seconde", "la troisième"])
>>> ordinaux[[[[1]
'seconde'
>>> ordinaux[[[[0:2]
Traceback (appel le plus récent en dernier) :
Fichier "" , ligne 1, dans
Erreur-type: l'index de séquence doit être un entier, pas une "tranche"
Deques prend en charge l'indexation mais, fait intéressant, ils ne prennent pas en charge le découpage. Lorsque vous essayez de récupérer une tranche d'un deque existant, vous obtenez un Erreur-type
. C'est parce que l'exécution d'une opération de découpage sur une liste chaînée serait inefficace, donc l'opération n'est pas disponible.
Gestion des clés manquantes : dict par défaut
Un problème courant auquel vous serez confronté lorsque vous travaillerez avec des dictionnaires en Python est de savoir comment gérer les clés manquantes. Si vous essayez d'accéder à une clé qui n'existe pas dans un dictionnaire donné, vous obtenez un KeyError
:
>>> favoris = "animal de compagnie": "chien", "Couleur": "bleu", "Langue": "Python"
>>> favoris[[[["fruit"]
Traceback (appel le plus récent en dernier) :
Fichier "" , ligne 1, dans
KeyError: 'fruit'
Il existe quelques approches pour contourner ce problème. Par exemple, vous pouvez utiliser .définir par defaut()
. Cette méthode prend une clé comme argument. Si la clé existe dans le dictionnaire, elle renvoie la valeur correspondante. Sinon, la méthode insère la clé, lui attribue une valeur par défaut et renvoie cette valeur :
>>> favoris = "animal de compagnie": "chien", "Couleur": "bleu", "Langue": "Python"
>>> favoris.définir par defaut("fruit", "Pomme")
'Pomme'
>>> favoris
'pet': 'dog', 'color': 'blue', 'language': 'Python', 'fruit': 'apple'
>>> favoris.définir par defaut("animal de compagnie", "chat")
'chien'
>>> favoris
'pet': 'dog', 'color': 'blue', 'language': 'Python', 'fruit': 'apple'
Dans cet exemple, vous utilisez .définir par defaut()
pour générer une valeur par défaut pour fruit
. Comme cette clé n'existe pas dans favoris
, .définir par defaut()
le crée et lui attribue la valeur de Pomme
. Si vous appelez .définir par defaut()
avec une clé existante, l'appel n'affectera pas le dictionnaire et votre clé conservera la valeur d'origine au lieu de la valeur par défaut.
Vous pouvez aussi utiliser .avoir()
pour renvoyer une valeur par défaut appropriée si une clé donnée est manquante :
>>> favoris = "animal de compagnie": "chien", "Couleur": "bleu", "Langue": "Python"
>>> favoris.avoir("fruit", "Pomme")
'Pomme'
>>> favoris
'pet': 'dog', 'color': 'blue', 'language': 'Python'
Ici, .avoir()
Retour Pomme
car la clé est manquante dans le dictionnaire sous-jacent. Pourtant, .avoir()
ne crée pas la nouvelle clé pour vous.
Étant donné que la gestion des clés manquantes dans les dictionnaires est un besoin courant, Python collections
fournit également un outil pour cela. Le dict par défaut
type est une sous-classe de dict
conçu pour vous aider avec les clés manquantes.
Le constructeur de dict par défaut
prend un objet fonction comme premier argument. Lorsque vous accédez à une clé qui n'existe pas, dict par défaut
appelle automatiquement cette fonction sans arguments pour créer une valeur par défaut appropriée pour la clé à portée de main.
Pour fournir sa fonctionnalité, dict par défaut
stocke la fonction d'entrée dans .default_factory
puis remplace .__manquant__()
pour appeler automatiquement la fonction et générer une valeur par défaut lorsque vous accédez à des clés manquantes.
Vous pouvez utiliser n'importe quel appelable pour initialiser votre dict par défaut
objets. Par exemple, avec entier()
vous pouvez créer un compteur pour compter différents objets :
>>> de collections importer dict par défaut
>>> compteur = dict par défaut(entier)
>>> compteur
defaultdict(, )
>>> compteur[[[["chiens"]
0
>>> compteur
defaultdict(, 'chiens': 0)
>>> compteur[[[["chiens"] += 1
>>> compteur[[[["chiens"] += 1
>>> compteur[[[["chiens"] += 1
>>> compteur[[[["chats"] += 1
>>> compteur[[[["chats"] += 1
>>> compteur
defaultdict(, 'chiens' : 3, 'chats' : 2)
Dans cet exemple, vous créez un vide dict par défaut
avec entier()
comme premier argument. Lorsque vous accédez à une clé qui n'existe pas, le dictionnaire appelle automatiquement entier()
, qui renvoie 0
comme valeur par défaut pour la clé à portée de main. Ce genre de dict par défaut
object est très utile lorsqu'il s'agit de compter des choses en Python.
Un autre cas d'utilisation courant de dict par défaut
est de grouper des choses. Dans ce cas, la fonction d'usine pratique est liste()
:
>>> de collections importer dict par défaut
>>> animaux domestiques = [[[[
... ("chien", "Affenpinscher"),
... ("chien", "Terrier"),
... ("chien", "Boxeur"),
... ("chat", "Abyssinien"),
... ("chat", "Birman"),
... ]
>>> groupe_animaux = dict par défaut(liste)
>>> pour animal de compagnie, élever dans animaux domestiques:
... groupe_animaux[[[[animal de compagnie].ajouter(élever)
...
>>> pour animal de compagnie, races dans groupe_animaux.éléments():
... imprimer(animal de compagnie, "->", races)
...
chien -> ['Affenpinscher', 'Terrier', 'Boxer']
chat -> ['Abyssinian', 'Birman']
Dans cet exemple, vous disposez de données brutes sur les animaux et leur race, et vous devez les regrouper par animal. Pour ce faire, vous utilisez liste()
comme .default_factory
lorsque vous créez le dict par défaut
exemple. Cela permet à votre dictionnaire de créer automatiquement une liste vide ([]
) comme valeur par défaut pour chaque clé manquante à laquelle vous accédez. Ensuite, vous utilisez cette liste pour stocker les races de vos animaux de compagnie.
Enfin, il faut noter que puisque dict par défaut
est une sous-classe de dict
, il fournit la même interface. Cela signifie que vous pouvez utiliser votre dict par défaut
objets comme vous utiliseriez un dictionnaire ordinaire.
Garder vos dictionnaires ordonnés : CommandéDict
Parfois, vous avez besoin que vos dictionnaires mémorisent l'ordre dans lequel les paires clé-valeur sont insérées. Les dictionnaires réguliers de Python étaient non ordonné structures de données pendant des années. Ainsi, en 2008, PEP 372 a introduit l'idée d'ajouter une nouvelle classe de dictionnaire à collections
.
La nouvelle classe se souviendrait de l'ordre des éléments en fonction du moment où les clés ont été insérées. Ce fut l'origine de CommandéDict
.
CommandéDict
a été introduit dans Python 3.1. Son interface de programmation d'applications (API) est sensiblement la même que dict
. Pourtant, CommandéDict
itère sur les clés et les valeurs dans le même ordre que les clés ont d'abord été insérées dans le dictionnaire. Si vous attribuez une nouvelle valeur à une clé existante, l'ordre de la paire clé-valeur reste inchangé. Si une entrée est supprimée et réinsérée, elle sera déplacée à la fin du dictionnaire.
Il existe plusieurs façons de créer CommandéDict
objets. La plupart d'entre eux sont identiques à la façon dont vous créez un dictionnaire ordinaire. Par exemple, vous pouvez créer un dictionnaire ordonné vide en instanciant la classe sans arguments, puis insérer des paires clé-valeur selon vos besoins :
>>> de collections importer CommandéDict
>>> Les étapes de la vie = CommandéDict()
>>> Les étapes de la vie[[[["enfance"] = "0-9"
>>> Les étapes de la vie[[[["adolescence"] = "9-18"
>>> Les étapes de la vie[[[["l'âge adulte"] = "18-65"
>>> Les étapes de la vie[[[["vieille"] = "+65"
>>> pour étape, ans dans Les étapes de la vie.éléments():
... imprimer(étape, "->", ans)
...
enfance -> 0-9
adolescence -> 9-18
âge adulte -> 18-65
vieux -> +65
Dans cet exemple, vous créez un dictionnaire ordonné vide en instanciant CommandéDict
sans argumentation. Ensuite, vous ajoutez des paires clé-valeur au dictionnaire comme vous le feriez avec un dictionnaire ordinaire.
Lorsque vous parcourez le dictionnaire, Les étapes de la vie
, vous obtenez les paires clé-valeur dans le même ordre que vous les avez insérées dans le dictionnaire. Garantir l'ordre des articles est le principal problème qui CommandéDict
résout.
Python 3.6 a introduit une nouvelle implémentation de dict
. Cette implémentation fournit une nouvelle fonctionnalité inattendue : désormais, les dictionnaires classiques conservent leurs éléments dans le même ordre qu'ils ont été insérés pour la première fois.
Initialement, la fonctionnalité était considérée comme un détail d'implémentation et la documentation conseillait de ne pas s'y fier. Cependant, depuis Python 3.7, la fonctionnalité fait officiellement partie de la spécification du langage. Alors, quel est l'intérêt d'utiliser CommandéDict
?
Il y a certaines caractéristiques de CommandéDict
qui le rendent encore précieux :
- Communication d'intention : Avec
CommandéDict
, votre code indiquera clairement que l'ordre des éléments dans le dictionnaire est important. Vous communiquez clairement que votre code a besoin ou repose sur l'ordre des éléments dans le dictionnaire sous-jacent. - Contrôle de l'ordre des articles : Avec
CommandéDict
, vous avez accès à.move_to_end()
, qui est une méthode qui vous permet de manipuler l'ordre des éléments dans votre dictionnaire. Vous aurez également une variation améliorée de.popitem()
qui permet de supprimer des éléments de chaque extrémité du dictionnaire sous-jacent. - Comportement du test d'égalité : Avec
CommandéDict
, les tests d'égalité entre dictionnaires tiennent compte de l'ordre des éléments. Ainsi, si vous avez deux dictionnaires ordonnés avec le même groupe d'éléments mais dans un ordre différent, alors vos dictionnaires seront considérés comme non égaux.
Il y a au moins une autre raison d'utiliser CommandéDict
: rétrocompatibilité. S'appuyer régulièrement dict
les objets pour préserver l'ordre des éléments casseront votre code dans les environnements qui exécutent des versions de Python antérieures à 3.6.
D'accord, il est maintenant temps de voir certaines de ces fonctionnalités intéressantes de CommandéDict
en action :
>>> de collections importer CommandéDict
>>> des lettres = CommandéDict(b=2, ré=4, une=1, c=3)
>>> des lettres
CommandéDict([('b', 2), ('d', 4), ('a', 1), ('c', 3)])
>>> # Déplacez b à l'extrémité droite
>>> des lettres.move_to_end("b")
>>> des lettres
CommandéDict([('d', 4), ('a', 1), ('c', 3), ('b', 2)])
>>> # Déplacez b vers l'extrémité gauche
>>> des lettres.move_to_end("b", dernier=Faux)
>>> des lettres
CommandéDict([('b', 2), ('d', 4), ('a', 1), ('c', 3)])
>>> # Trier les lettres par clé
>>> pour clé dans trié(des lettres):
... des lettres.move_to_end(clé)
...
>>> des lettres
CommandéDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
Dans ces exemples, vous utilisez .move_to_end()
pour déplacer des articles et réorganiser des lettres
. Notez que .move_to_end()
accepte un argument optionnel appelé dernier
qui vous permet de contrôler à quelle extrémité du dictionnaire vous souhaitez déplacer les éléments. Cette méthode est très pratique lorsque vous devez trier les éléments de vos dictionnaires ou lorsque vous devez manipuler leur ordre de quelque manière que ce soit.
Une autre différence importante entre CommandéDict
et un dictionnaire ordinaire est la façon dont ils se comparent pour l'égalité :
>>> de collections importer CommandéDict
>>> # Les dictionnaires normaux ne comparent que le contenu
>>> lettres_0 = dict(une=1, b=2, c=3, ré=4)
>>> lettres_1 = dict(b=2, une=1, ré=4, c=3)
>>> lettres_0 == lettres_1
Vrai
>>> # Les dictionnaires ordonnés comparent le contenu et l'ordre
>>> lettres_0 = CommandéDict(une=1, b=2, c=3, ré=4)
>>> lettres_1 = CommandéDict(b=2, une=1, ré=4, c=3)
>>> lettres_0 == lettres_1
Faux
>>> lettres_2 = CommandéDict(une=1, b=2, c=3, ré=4)
>>> lettres_0 == lettres_2
Vrai
Ici, lettres_1
a une commande d'article différente de lettres_0
. Lorsque vous utilisez des dictionnaires normaux, cette différence n'a pas d'importance et les deux dictionnaires se comparent égaux. En revanche, lorsque vous utilisez des dictionnaires ordonnés, lettres_0
et lettres_1
ne sont pas égaux. En effet, les tests d'égalité entre les dictionnaires ordonnés prennent en compte le contenu et également l'ordre des éléments.
Comptage d'objets en une seule fois : Compteur
Le comptage d'objets est une opération courante en programmation. Supposons que vous deviez compter combien de fois un élément donné apparaît dans une liste ou un itérable. Si votre liste est courte, le comptage de ses éléments peut être simple et rapide. Si vous avez une longue liste, il sera plus difficile de compter les éléments.
To count objects, you typically use a compteur, or an integer variable with an initial value of zero. Then you increment the counter to reflect the number of times a given object occurs.
In Python, you can use a dictionary to count several different objects at once. In this case, the keys will store individual objects, and the values will hold the number of repetitions of a given object, or the object’s compter.
Here’s an example that counts the letters in the word "mississippi"
with a regular dictionary and a pour
loop:
>>> mot = "mississippi"
>>> compteur =
>>> pour lettre dans mot:
... si lettre ne pas dans compteur:
... compteur[[[[lettre] = 0
... compteur[[[[lettre] += 1
...
>>> compteur
'm': 1, 'i': 4, 's': 4, 'p': 2
The loop iterates over the letters in mot
. The conditional statement checks if the letters aren’t already in the dictionary and initializes the letter’s count to zero accordingly. The final step is to increment the letter’s count as the loop goes.
As you already know, defaultdict
objects are convenient when it comes to counting things because you don’t need to check if the key exists. The dictionary guarantees appropriate default values for any missing keys:
>>> from collections importer defaultdict
>>> compteur = defaultdict(entier)
>>> pour lettre dans "mississippi":
... compteur[[[[lettre] += 1
...
>>> compteur
defaultdict(, 'm': 1, 'i': 4, 's': 4, 'p': 2)
In this example, you create a defaultdict
object and initialize it using int()
. Avec int()
as a factory function, the underlying default dictionary automatically creates missing keys and conveniently initializes them to zero. Then you increment the value of the current key to compute the final count of the letter in "mississippi"
.
Just like with other common programming problems, Python also has an efficient tool for approaching the counting problem. Dans collections
, you’ll find Counter
, which is a dict
subclass specially designed for counting objects.
Here’s how you can write the "mississippi"
example using Counter
:
>>> from collections importer Counter
>>> Counter("mississippi")
Counter('i': 4, 's': 4, 'p': 2, 'm': 1)
Wow! That was quick! A single line of code and you’re done. In this example, Counter
iterates over "mississippi"
, producing a dictionary with the letters as keys and their frequency as values.
There are a few different ways to instantiate Counter
. You can use lists, tuples, or any iterables with repeated objects. The only restriction is that your objects need to be hashable:
>>> from collections importer Counter
>>> Counter([[[[1, 1, 2, 3, 3, 3, 4])
Counter(3: 3, 1: 2, 2: 1, 4: 1)
>>> Counter(([[[[1], [[[[1]))
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'
Integer numbers are hashable, so Counter
works correctly. On the other hand, lists aren’t hashable, so Counter
fails with a TypeError
.
Being hashable means that your objects must have a hash value that never changes during their lifetime. This is a requirement because these objects will work as dictionary keys. In Python, immutable objects are also hashable.
Note: Dans Counter
, a highly optimized C function provides the counting functionality. If this function isn’t available for some reason, then the class uses an equivalent but less efficient Python function.
Depuis Counter
is a subclass of dict
, their interfaces are mostly the same. However, there are some subtle differences. The first difference is that Counter
doesn’t implement .fromkeys()
. This avoids inconsistencies, such as Counter.fromkeys("abbbc", 2)
, in which every letter would have an initial count of 2
regardless of the real count it has in the input iterable.
The second difference is that .update()
doesn’t replace the count (value) of an existing object (key) with a new count. It adds both counts together:
>>> from collections importer Counter
>>> des lettres = Counter("mississippi")
>>> des lettres
Counter('i': 4, 's': 4, 'p': 2, 'm': 1)
>>> # Update the counts of m and i
>>> des lettres.mettre à jour(m=3, je=4)
>>> des lettres
Counter('i': 8, 'm': 4, 's': 4, 'p': 2)
>>> # Add a new key-count pair
>>> des lettres.mettre à jour("a": 2)
>>> des lettres
Counter('i': 8, 'm': 4, 's': 4, 'p': 2, 'a': 2)
>>> # Update with another counter
>>> des lettres.mettre à jour(Counter([[[["s", "s", "p"]))
>>> des lettres
Counter('i': 8, 's': 6, 'm': 4, 'p': 3, 'a': 2)
Here, you update the count for m
et je
. Now those letters hold the somme of their initial count plus the value you passed to them through .update()
. If you use a key that isn’t present in the original counter, then .update()
creates the new key with the corresponding value. Finally, .update()
accepts iterables, mappings, keyword arguments, and also other counters.
Note: Depuis Counter
is a subclass of dict
, there are no restrictions on the objects you can store in the keys and values of your counters. The keys can store any hashable objects, whereas the values can store any objects. However, to logically work as counters, the values should be integer numbers representing counts.
Another difference between Counter
et dict
is that accessing a missing key returns 0
instead of raising a KeyError
:
>>> from collections importer Counter
>>> des lettres = Counter("mississippi")
>>> des lettres[[[["a"]
0
This behavior signals that the count of an object that doesn’t exist in the counter is zero. In this example, the letter "a"
isn’t in the original word, so its count is 0
.
In Python, Counter
is also useful to emulate a multiset or sac. Multisets are similar to sets, but they allow multiple instances of a given element. The number of instances of an element is known as its multiplicity. For example, you can have a multiset like 1, 1, 2, 3, 3, 3, 4, 4.
When you use Counter
to emulate multisets, the keys represent the elements, and the values represent their respective multiplicity:
>>> from collections importer Counter
>>> multiset = Counter(1, 1, 2, 3, 3, 3, 4, 4)
>>> multiset
Counter(1: 1, 2: 1, 3: 1, 4: 1)
>>> multiset.clés() == 1, 2, 3, 4
True
Here, the keys of multiset
are equivalent to a Python set. The values hold the multiplicity of each element in the set.
Python’ Counter
provides a few additional features that help you work with them as multisets. For example, you can initialize your counters with a mapping of elements and their multiplicity. You can also perform math operations on the elements’ multiplicity and more.
Say you’re working at the local pet shelter. You have a given number of pets, and you need to have a record of how many pets are adopted each day and how many pets enter and leave the shelter. In this case, you can use Counter
:
>>> from collections importer Counter
>>> inventaire = Counter(chiens=23, chats=14, pythons=7)
>>> adopté = Counter(chiens=2, chats=5, pythons=1)
>>> inventaire.subtract(adopté)
>>> inventaire
Counter('dogs': 21, 'cats': 9, 'pythons': 6)
>>> new_pets = "dogs": 4, "cats": 1
>>> inventaire.mettre à jour(new_pets)
>>> inventaire
Counter('dogs': 25, 'cats': 10, 'pythons': 6)
>>> inventaire = inventaire - Counter(chiens=2, chats=3, pythons=1)
>>> inventaire
Counter('dogs': 23, 'cats': 7, 'pythons': 5)
>>> new_pets = "dogs": 4, "pythons": 2
>>> inventaire += new_pets
>>> inventaire
Counter('dogs': 27, 'cats': 7, 'pythons': 7)
That’s neat! Now you can keep a record of your pets using Counter
. Note that you can use .subtract()
et .update()
to subtract and add counts or multiplicities. You can also use the addition (+
) and subtraction (-
) operators.
There’s a lot more you can do with Counter
objects as multisets in Python, so go ahead and give it a try!
Chaining Dictionaries Together: ChainMap
Python’s ChainMap
groups multiple dictionaries and other mappings together to create a single object that works pretty much like a regular dictionary. In other words, it takes several mappings and makes them logically appear as one.
ChainMap
objects are updateable views, which means that changes in any of the chained mappings affect the ChainMap
object as a whole. This is because ChainMap
doesn’t merge the input mappings together. It keeps a list of mappings and reimplements common dictionary operations on top of that list. For example, a key lookup searches the list of mappings successively until it finds the key.
When you’re working with ChainMap
objects, you can have several dictionaries with either unique or repeated keys.
In either case, ChainMap
allows you to treat all your dictionaries as one. If you have unique keys across your dictionaries, you can access and update the keys as if you were working with a single dictionary.
If you have repeated keys across your dictionaries, besides managing your dictionaries as one, you can also take advantage of the internal list of mappings to define some sort of access priority. Because of this feature, ChainMap
objects are great for handling multiple contexts.
For example, say you’re working on a command-line interface (CLI) application. The application allows the user to use a proxy service for connecting to the Internet. The settings priorities are:
- Command-line options (
--proxy
,-p
) - Local configuration files in the user’s home directory
- Global proxy configuration
If the user supplies a proxy at the command line, then the application must use that proxy. Otherwise, the application should use the proxy provided in the next configuration object, and so on. This is one of the most common use cases of ChainMap
. In this situation, you can do the following:
>>> from collections importer ChainMap
>>> cmd_proxy = # The user doesn't provide a proxy
>>> local_proxy = "proxy": "proxy.local.com"
>>> global_proxy = "proxy": "proxy.global.com"
>>> configuration = ChainMap(cmd_proxy, local_proxy, global_proxy)
>>> configuration[[[["proxy"]
'proxy.local.com'
ChainMap
allows you to define the appropriate priority for the application’s proxy configuration. A key lookup searches cmd_proxy
, ensuite local_proxy
, and finally global_proxy
, returning the first instance of the key at hand. In this example, the user doesn’t provide a proxy at the command line, so your application uses the proxy in local_proxy
.
In general, ChainMap
objects behave similarly to regular dict
objects. However, they have some additional features. For example, they have a .maps
public attribute that holds the internal list of mappings:
>>> from collections importer ChainMap
>>> Nombres = "one": 1, "two": 2
>>> des lettres = "a": "A", "b": "B"
>>> alpha_nums = ChainMap(Nombres, des lettres)
>>> alpha_nums.Plans
['one': 1, 'two': 2, 'a': 'A', 'b': 'B']
The instance attribute .maps
gives you access to the internal list of mappings. This list is updatable. You can add and remove mappings manually, iterate through the list, and more.
Additionally, ChainMap
provides a .new_child()
method and a .parents
property:
>>> from collections importer ChainMap
>>> papa = "name": "John", "age": 35
>>> maman = "name": "Jane", "age": 31
>>> famille = ChainMap(maman, papa)
>>> famille
ChainMap('name': 'Jane', 'age': 31, 'name': 'John', 'age': 35)
>>> fils = "name": "Mike", "age": 0
>>> famille = famille.new_child(fils)
>>> pour personne dans famille.Plans:
... imprimer(personne)
...
'name': 'Mike', 'age': 0
'name': 'Jane', 'age': 31
'name': 'John', 'age': 35
>>> famille.Parents
ChainMap('name': 'Jane', 'age': 31, 'name': 'John', 'age': 35)
Avec .new_child()
, you create a new ChainMap
object containing a new map (fils
) followed by all the maps in the current instance. The map passed as a first argument becomes the first map in the list of maps. If you don’t pass a map, then the method uses an empty dictionary.
Le Parents
property returns a new ChainMap
objects containing all the maps in the current instance except for the first one. This is useful when you need to skip the first map in a key lookup.
A final feature to highlight in ChainMap
is that mutating operations, such as updating keys, adding new keys, deleting existing keys, popping keys, and clearing the dictionary, act on the first mapping in the internal list of mappings:
>>> from collections importer ChainMap
>>> Nombres = "one": 1, "two": 2
>>> des lettres = "a": "A", "b": "B"
>>> alpha_nums = ChainMap(Nombres, des lettres)
>>> alpha_nums
ChainMap('one': 1, 'two': 2, 'a': 'A', 'b': 'B')
>>> # Add a new key-value pair
>>> alpha_nums[[[["c"] = "C"
>>> alpha_nums
ChainMap('one': 1, 'two': 2, 'c': 'C', 'a': 'A', 'b': 'B')
>>> # Pop a key that exists in the first dictionary
>>> alpha_nums.pop("two")
2
>>> alpha_nums
ChainMap('one': 1, 'c': 'C', 'a': 'A', 'b': 'B')
>>> # Delete keys that don't exist in the first dict but do in others
>>> del alpha_nums[[[["a"]
Traceback (most recent call last):
...
KeyError: "Key not found in the first mapping: 'a'"
>>> # Clear the dictionary
>>> alpha_nums.dégager()
>>> alpha_nums
ChainMap(, 'a': 'A', 'b': 'B')
These examples show that mutating operations on a ChainMap
object only affect the first mapping in the internal list. This is an important detail to consider when you’re working with ChainMap
.
The tricky part is that, at first glance, it could look like it’s possible to mutate any existing key-value pair in a given ChainMap
. However, you can only mutate the key-value pairs in the first mapping unless you use .maps
to access and mutate other mappings in the list directly.
Customizing Built-Ins: UserString
, UserList
, et UserDict
Sometimes you need to customize built-in types, such as strings, lists, and dictionaries to add and modify certain behavior. Since Python 2.2, you can do that by subclassing those types directly. However, you could face some issues with this approach, as you’ll see in a minute.
Python’s collections
provides three convenient wrapper classes that mimic the behavior of the built-in data types:
UserString
UserList
UserDict
With a combination of regular and special methods, you can use these classes to mimic and customize the behavior of strings, lists, and dictionaries.
Nowadays, developers often ask themselves if there’s a reason to use UserString
, UserList
, et UserDict
when they need to customize the behavior of built-in types. La réponse est oui.
Built-in types were designed and implemented with the open-closed principle in mind. This means that they’re open for extension but closed for modification. Allowing modifications on the core features of these classes can potentially break their invariants. So, Python core developers decided to protect them from modifications.
For example, say you need a dictionary that automatically lowercases the keys when you insert them. You could subclass dict
and override .__setitem__()
so every time you insert a key, the dictionary lowercases the key name:
>>> classer LowerDict(dict):
... déf __setitem__(soi, clé, valeur):
... clé = clé.inférieur()
... super().__setitem__(clé, valeur)
...
>>> ordinals = LowerDict("FIRST": 1, "SECOND": 2)
>>> ordinals[[[["THIRD"] = 3
>>> ordinals.mettre à jour("FOURTH": 4)
>>> ordinals
'FIRST': 1, 'SECOND': 2, 'third': 3, 'FOURTH': 4
>>> isinstance(ordinals, dict)
True
This dictionary works correctly when you insert new keys using dictionary-style assignment with square brackets ([]
). However, it doesn’t work when you pass an initial dictionary to the class constructor or when you use .update()
. This means that you would need to override .__init__()
, .update()
, and probably some other methods for your custom dictionary to work correctly.
Now take a look at the same dictionary but using UserDict
as a base class:
>>> from collections importer UserDict
>>> classer LowerDict(UserDict):
... déf __setitem__(soi, clé, valeur):
... clé = clé.inférieur()
... super().__setitem__(clé, valeur)
...
>>> ordinals = LowerDict("FIRST": 1, "SECOND": 2)
>>> ordinals[[[["THIRD"] = 3
>>> ordinals.mettre à jour("FOURTH": 4)
>>> ordinals
'first': 1, 'second': 2, 'third': 3, 'fourth': 4
>>> isinstance(ordinals, dict)
False
It works! Your custom dictionary now converts all the new keys into lowercase letters before inserting them into the dictionary. Note that since you don’t inherit from dict
directly, your class doesn’t return instances of dict
as in the example above.
UserDict
stores a regular dictionary in an instance attribute called .data
. Then it implements all its methods around that dictionary. UserList
et UserString
work the same way, but their .data
attribute holds a liste
and a str
object, respectively.
If you need to customize either of these classes, then you just need to override the appropriate methods and change what they do as required.
In general, you should use UserDict
, UserList
, et UserString
when you need a class that acts almost identically to the underlying wrapped built-in class and you want to customize some part of its standard functionalities.
Another reason to use these classes rather than the built-in equivalent classes is to access the underlying .data
attribute to manipulate it directly.
The ability to inherit from built-in types directly has largely superseded the use of UserDict
, UserList
, et UserString
. However, the internal implementation of built-in types makes it hard to safely inherit from them without rewriting a significant amount of code. In most cases, it’s safer to use the appropriate class from collections
. It’ll save you from several issues and weird behaviors.
Conclusion
In Python’s collections
module, you have several specialized container data types that you can use to approach common programming problems, such as counting objects, creating queues and stacks, handling missing keys in dictionaries, and more.
The data types and classes in collections
were designed to be efficient and Pythonic. They can be tremendously helpful in your Python programming journey, so learning about them is well worth your time and effort.
In this tutorial, you learned how to:
- Write readable et explicit code using
namedtuple
- Build efficient queues et stacks en utilisant
deque
- Count objects efficiently using
Counter
- Handle missing dictionary keys avec
defaultdict
- Remember the insertion order of keys with
OrderedDict
- Chain multiple dictionaries in a single view with
ChainMap
You also learned about three convenient wrapper classes: UserDict
, UserList
, et UserString
. These classes are handy when you need to create custom classes that mimic the behavior of the built-in types dict
, liste
, et str
.
[ad_2]