Dictionnaires¶
L’essentiel¶
Les dictionnaires : présentation¶
Supposons qu’on ait une liste d’individus et qu’on veuille stocker leur âge. On souhaiterait disposer d’une structure de données qui à partir du nom d’un individu, donnerait son âge. On souhaiterait en outre que l’accès à l’âge soit « instantané » ou tout du moins rapide, un peu comme l’accès à un élément d’une liste est immédiat à partir de son indice. Un telle structure de données existe et s’appelle un dictionnaire ou encore un mapping. Le langage Python dispose nativement de dictionnaires.
Voici un exemple de création et d’utilisation de dictionnaire en Python :
1 2 3 4 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(d["thierry"])
print(d["jeanne"])
|
5 6 | 40
18
|
- Ligne 1 :
d
est le dictionnaire ; il associe des prénoms à un âge. - Lignes 2 et 3 : on accède en lecture à l’âge de
thierry
puis à l’âge dejeanne
Un dictionnaire répertorie des items. Un item est une paire formée d’une clé et de la valeur correspondante de cette clé. Dans l’exemple ci-dessus, la paire ("thierry", 40)
est un item, "thierry"
est une clé du dictionnaire d
et 40
est la valeur de la clé "thierry"
. Le dictionnaire ci-dessus contient 5 clés. Ici, les clés sont du type chaîne et les valeurs sont de type entier.
Non seulement les dictionnaires sont un type de données (au-delà de tout langage de programmation) mais, en Python, ils définissent un type, le type dict
:
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(type(d))
<class 'dict'>
Lien vers la documentation officielle des dictionnaires : Mapping Types: dict.
Situations d’utilisation d’un dictionnaire¶
Les dictionnaires sont très utiles et très courants d’emploi. Voici, en vrac, quelques situations où un dictionnaire peut s’avérer utile :
- ville → température
- nom → âge
- nom → taille
- nom → (âge, taille, téléphone)
- polynôme (polynôme creux plus précisément) : exposant → coefficient
- dictionnaire français-anglais
- nom → surnom
- valeur dans une liste → fréquence
- pour coder les exceptions du pluriel : « bijou » → « bijoux »
- codage en décalage dit en rot13 : « A » → « N », « B » → « O », etc
- émulation d’une structure des langages C ou C++
mais en réalité, les situations sont innombrables.
Dictionnaire littéral¶
On peut créer un dictionnaire en Python en plaçant les items entre accolades, séparés par des virgules. Chaque item est entré en plaçant successivement :
- la clé,
- le séparateur deux-points
- la valeur de la clé.
Exemple :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
On crée ainsi un dictionnaire qui est un objet Python et auquel on peut se référer en utilisant une variable, ici d
. Un dictionnaire créé de cette façon dans le code-source est appelé un dictionnaire littéral.
Dans un dictionnaire, une clé ou une valeur étant des objets Python, elles peuvent être définies par des expressions utilisant des variables :
1 2 3 4 5 6 7 | p = "paul"
age = 25
d = {p: age, "jean" : 70, "thierry" : 40, "léa" : age - 10, "jeanne" : 18 }
print(d[p])
print(d["léa"])
|
8 9 | 25
15
|
- Ligne 4 : la clé et la valeur du premier item est sont référencés par des variables définies précédemment.
- Ligne 4 : la valeur de la clé
"léa"
est celle d’une expression dépendant de la variableage
.
Voir plus bas ce qu’il se passe si deux clés sont identiques dans le dictionnaire littéral.
Facilité d’édition d’un dictionnaire littéral¶
Un dictionnaire littéral peut être édité avec certaines facilités de formatage afin d’en améliorer la lisibilité dans le code-source.
Par exemple, soit le dictionnaire :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
Il est équivalent de le saisir dans le code-source Python de la manière suivante :
d = {
"paul" : 25,
"jean" : 70,
"thierry" : 40,
"léa" : 15,
"jeanne" : 18
}
Les sauts de lignes entre les accolades ont permis d’aligner les clés entre elles ainsi que les valeurs correspondantes : le dictionnaire est plus lisible.
Affichage d’un dictionnaire¶
Si d
est un dictionnaire, print(d)
affiche d
sous forme de dictionnaire littéral :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(d)
{'paul': 25, 'jeanne': 18, 'thierry': 40, 'jean': 70, 'léa': 15}
Le dictionnaire est affiché sous forme littérale. Depuis Python 3.7, l’ordre d’affichage des items correspond toujours à l’ordre d’insertion des items dans le dictionnaire.
Unicité de chaque clé¶
Dans un dictionnaire, chaque clé est unique, autrement dit, deux items distincts ont des clés distinctes.
Dans un dictionnaire littéral, si on place plusieurs items ayant même clé, c’est le dernier item qui est pris en compte, les précédents items ayant ce nom sont ignorés :
1 2 3 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15,
"thierry" : 45, "jeanne" : 18 }
print(d["thierry"])
|
4 | 45
|
- Lignes 1 et 2 : la clé
"thierry"
est présente deux fois. - Lignes 2 et 4 : c’est la deuxième occurrence, celle qui porte la valeur 45, qui est prise en compte.
Longueur d’un dictionnaire¶
Un dictionnaire est un conteneur de clés et il contient donc un certain nombre d’éléments. Ce nombre est obtenu en utilisant la fonction len
:
d = {"paul" : 25, "jean" : 70, "thierry" : 40,
"léa" : 15, "thierry" : 45, "jeanne" : 18 }
print(len(d))
5
- le nombre d’éléments du dictionnaire. On observera que les deux entrées
"thierry" : 40
et"thierry" : 45
ne sont comptées qu’une seule fois.
La longueur d’un dictionnaire est aussi son nombre de clés distinctes.
Dictionnaire vide¶
Un dictionnaire peut être vide :
d = {}
print(len(d))
0
Un dictionnaire vide trouve son intérêt, il peut être le conteneur de base d’un dictionnaire qui va grossir.
Lecture d’une valeur d’un dictionnaire¶
Un dictionnaire permet d’accéder à partir d’une de ses clés à la valeur de l’item. Pour lire cette valeur, on utilise l’indexation par la clé, avec une paire de crochets, comme pour une liste :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(d["thierry"])
print(d["jeanne"])
40
18
Un dictionnaire est un peu comme une liste mais indexée par des clés et non par des entiers.
Clé absente¶
Si on tente d’accéder à une clé qui ne figure pas dans le dictionnaire, on déclenche une erreur :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(d["pascal"])
KeyError: 'pascal'
- Erreur sur la clé (
KeyError
) : la clé"pascal"
n’existe pas dans le dictionnaired
.
Appartenance à un dictionnaire¶
Le point suivant est un des éléments qui font tout l’intérêt des dictionnaires. Il est possible de tester la présence dans un dictionnaire d’une clé avec l’opérateur d’appartenance in
:
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print("pascal" in d)
False
L’opérateur not in
permet de tester la non-appartenance :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print("pascal" not in d)
True
Le type des clés¶
Les clés d’un dictionnaire sont fréquemment des entiers ou des chaînes de caractères. Toute clé d’un dictionnaire est un objet Python, et donc, toute clé a un type.
Clés hachables¶
Toutefois, certains types ne peuvent représenter des clés de dictionnaire. Par exemple, une clé ne peut être de type liste :
cle_invalide.py
d = {"paul" : 25, ["thierry", "pierre"]: 40}
print(d)
TypeError: unhashable type: 'list'
Une clé de dictionnaire doit avoir un type dit « hachable ». C’est le cas pour les types entier, chaîne de caractères et tuple.
Ainsi, on peut transformer l’exemple cle_invalide.py
pour obtenir un dictionnaire valide en utilisant un tuple plutôt qu’une liste :
d = {"paul" : 25, ("thierry", "pierre"): 40}
print(d)
{'paul': 25, ('thierry', 'pierre'): 40}
d
possède deux clés : une chaîne et un tuple de deux chaînes.
\(100\%\) hachable¶
Tout objet de type tuple, néanmoins, n’est pas forcément hachable. Si le tuple contient, directement ou indirectement, un objet de type non-hachable, le tuple sera non hachable :
t = (42, [421, 100])
d = {5 : 10, t: 10}
TypeError: unhashable type: 'list'
t
est un tuple mais un des membres de ce tuple est une liste qui est un type non hachable- Il en résulte que
t
ne peut figurer comme clé d’un dictionnaire.
En revanche, il n’y a aucune restriction sur les types des valeurs des clés.
Modifier un dictionnaire¶
Étant donné un dictionnaire d
, il est possible :
- de modifier la valeur d’une clé déjà présente ;
- d’ajouter des items au dictionnaire.
1 2 3 4 5 6 7 8 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(d["paul"])
d["paul"]=26
print(d["paul"])
d["pascal"]=50
print(d)
|
9 10 11 | 25
26
{'paul': 26, 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18, 'pascal': 50}
|
- Ligne 4 : on écrase la valeur de la clé « paul »
- Ligne 7 : on ajoute l’item
"pascal : 50
.
On observera que c’est la même syntaxe qui est utilisée pour à la fois
- modifier la valeur d’une clé
- placer une nouvelle clé.
Les modifications ci-dessus traduisent qu’un dictionnaire est, comme une liste, un objet mutable.
Parcours de dictionnaire¶
Un dictionnaire est un conteneur. Il est possible de parcourir ce conteneur pour examiner les clés ainsi que les valeurs correspondantes.
1 2 3 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18}
for k in d:
print(k, "->", d[k])
|
4 5 6 7 8 | paul -> 25
jean -> 70
thierry -> 40
léa -> 15
jeanne -> 18
|
Le parcours du dictionnaire s’effectue à l’aide d’une boucle for
. Pour parcourir le dictionnaire, on parcourt les clés (cf. ligne 2), ce qui permet d’accéder à la valeur associée à la clé.
L’ordre de parcours du dictionnaire est celui de l’ordre d’insertion des clés dans le dictionnaire.
Dictionnaire en compréhension¶
Soit par exemple à créer un dictionnaire dont les items aient pour clé chacun des 8 chiffres romains
I, V, X, L, C, D, M
en sorte que la valeur de chaque item soit la valeur numérique correspondant au chiffre romain, par exemple, la valeur de l’item X est 10.
Cette question est commodément résolue en utilisant la syntaxe des dictionnaires en compréhension :
1 2 3 4 5 | romains="IVXLCDM"
valeurs=[1, 5, 10, 50, 100, 500, 1000]
d = {romains[i] : valeurs[i] for i in range(len(romains))}
print(d)
|
6 | {'C': 100, 'D': 500, 'I': 1, 'M': 1000, 'L': 50, 'V': 5, 'X': 10}
|
- Ligne 4 : la syntaxe du membre de droite est celle d’un dictionnaire en compréhension. L’indice i parcourt tous les indices possibles des éléments de la chaîne romains (les chiffres romains). Ici,
romains[i]
est la clé de l’item (un chiffre romain) et la valeur correspondante de l’item estvaleurs[i]
qui est l’entier qui correspond à la valeur représentée par le chiffre romain, par exemple « X » représente 10. - Ligne 4 : pour simplifier l’exposé, ce n’est pas la façon considérée comme pythonique de parcourir simultanément 2 itérables qui a été utilisée.
La syntaxe du dictionnaire en compréhension est calquée sur celle des listes en compréhension. En particulier, il est possible d’utiliser une clause if
.
Garnir un dictionnaire à partir d’un dictionnaire vide¶
Note : les situations décrites ci-après sont souvent mieux traitées par utilisation d’un dictionnaire en compréhension ou d’autres méthodes de création de dictionnaire.
On peut créer un dictionnaire en remplissant item par item un dictionnaire au départ vide :
d={}
d["paul"] = 25
d["jean"] = 70
d["thierry"] = 40
d["léa"] =15
d["thierry"] = 45
d["jeanne"] = 18
print(d)
{'paul': 25, 'jean': 70, 'thierry': 45, 'léa': 15, 'jeanne': 18}
mais, présenté comme ci-dessus, cela a peu d’intérêt.
Usage d’une boucle¶
En pratique, les items sont souvent entrées dans le dictionnaire à l’aide d’une boucle. Par exemple, soit la liste des cinq voyelles "A"
, "E"
, "I"
, "O"
, "U"
, "Y"
et soit le dictionnaire dont les items sont les cinq voyelles et la valeur de l’item, le rang de la voyelle ("A"
est la voyelle n°1, "E"
la voyelle n°2, etc) :
voyelles = "AEIOUY"
d={}
i=1
for v in voyelles:
d[v] = i
i = i + 1
print(d)
{'A': 1, 'E': 2, 'I': 3, 'O': 4, 'U': 5, 'Y': 6}
L’exemple ci-dessus serait avantageusement traité par un dictionnaire en compréhension :
d = {v:i for i, v in enumerate(voyelles, 1)}
print(d)
{'A': 1, 'E': 2, 'I': 3, 'O': 4, 'U': 5, 'Y': 6}
Une autre possibilité est d’utiliser dict
:
d = dict(zip(voyelles, range(1, len(voyelles)+1)))
print(d)
{'A': 1, 'E': 2, 'I': 3, 'O': 4, 'U': 5, 'Y': 6}
Création de dictionnaire avec dict
¶
Il existe d’autres procédés de création de dictionnaire que la construction d’un dictionnaire littéral. On peut construire un dictionnaire avec le type dict
. Le processus est expliqué à l’exemple suivant :
1 2 3 4 | mes_items = [["paul", 25], ["jeanne", 18],
["thierry", 45], ["jean", 70], ["léa", 15]]
d = dict(mes_items)
print(d)
|
5 | {'paul': 25, 'jeanne': 18, 'thierry': 45, 'jean': 70, 'léa': 15}
|
- Lignes 1-2 : pour créer un dictionnaire formé de clés et des valeurs correspondantes, on écrit chaque item comme une liste de deux éléments (clé et valeur) et on place tous ces items dans une liste.
- Ligne 3 : on appelle ensuite le constructeur
dict
sur cette liste. L’appel renvoie alors le dictionnaire souhaité.
dict
est souvent appelé un contructeur puisqu’il permet de construire des objets d’une certaine catégorie.
Supprimer une clé d’un dictionnaire¶
Un dictionnaire étant mutable une clé d’un dictionnaire peut être supprimée : il suffit d’utiliser l’instruction del
appliquée à la clé à supprimer :
1 2 3 4 5 | d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
print(d)
del d["oslo"]
print(d)
|
6 7 | {'paris': 30, 'madrid': 35, 'oslo': 20}
{'paris': 30, 'madrid': 35}
|
- Ligne 4 : supression de la clé
"oslo"
du dictionnaired
. - Ligne 7 : le dictionnaire
d
a bien perdu une clé.
del
supprime une référence¶
En réalité, l’instruction del
ne supprime la clé que du dictionnaire, autrement dit, del
ne supprime que la référence que le dictionnaire contient vers l’item. La clé et la valeur peuvent très bien encore exister en mémoire :
1 2 3 4 5 6 7 8 9 | oslo = "oslo"
temp_oslo = 20
d = {"paris" : 30, "madrid" : 35, oslo : temp_oslo}
print(d)
del d[oslo]
print(d)
print(oslo, temp_oslo)
|
10 11 12 | {'paris': 30, 'madrid': 35, 'oslo': 20}
{'paris': 30, 'madrid': 35}
oslo 20
|
- Ligne 4 : la clé
"oslo"
est référencée via la variableoslo
; de même, la valeur de la clé dans le dictionnaire est référencée par la variabletemp_oslo
. - Lignes 7 et 11 : la clé
"oslo"
ded
est supprimée. - Lignes 9 et 12 : les objets que référençaient la clé et la valeur de la clé subsistent.
Clé absente¶
Si on essaye de supprimer un item d’un dictionnaire d
par une clé qui ne figure pas dans d
, une erreur se produit :
1 2 | d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
del d["new-york"]
|
3 | KeyError: 'new-york'
|
- Ligne 3 :
d
ne contient pas de clé"new-york"
.
Plusieurs clés¶
Il est possible de supprimer plusieurs clés en une seule instruction del
:
d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
del d["paris"], d["madrid"]
print(d)
{'oslo': 20}
Fusionner deux dictionnaires¶
Étant donné deux dictionnaires d1
et d2
, la méthode update
permet de fusionner d1
et d2
en d1
tout en laissant intact d2
:
d1 = {"paris" : 30, "madrid" : 35, "oslo" : 20}
d2 = {"new-york" : 28, "madrid" : 40}
print(d1)
print(d2)
print()
d1.update(d2)
print(d1)
print(d2)
{'paris': 30, 'madrid': 35, 'oslo': 20}
{'new-york': 28, 'madrid': 40}
{'paris': 30, 'madrid': 40, 'oslo': 20, 'new-york': 28}
{'new-york': 28, 'madrid': 40}
D’une façon générale, si d1
et d2
sont deux dictionnaires alors d1.update(d2)
étend le dictionnaire d1
de tous les items de d2
. Le dictionnaire d1
est donc en général modifié par la méthode update
. Plus précisément
- si
k
est une clé présente dansd1
etd2
, la valeur dek
sera remplacée dansd1
par la valeur dek
dansd2
- si
k
est une clé ded2
non présente dansd1
, alorsk
deviendra une nouvelle clé ded1
et la valeur dek
sera la valeur dek
dansd2
.
Attention, l’appel d1.update(d2)
modifie d1
et renvoie None
et non pas le dictionnaire d1
mis-à-jour.
Opérateur de fusion de dictionnaires¶
Étant donné deux dictionnaires d1
et d2
, alors l’opération d1 | d2
crée un dictionnaire qui est la fusion des deux dictionnaires d1
et d2
. Cette fonctionnalité est disponible depuis la version 3.9. de Python. Voici un exemple d’utilisation :
d1 = {"paul" : 25, "jean" : 70, "thierry" : 40}
d2 = {"léa" : 15, "jean" : 50, "adam" : 1}
print(d1 | d2)
{'paul': 25, 'jean': 50, 'thierry': 40, 'léa': 15, 'adam': 1}
d1 | d2
possède les items de d1
et de d2
. En cas de conflit de clés, comme dans l’exemple avec la clé "jean"
, c’est l’item du 2e dictionnaire qui est choisi, en sorte que les dictionnaires d1 | d2
et d2 | d1
ne sont pas forcément égaux. Noter que cette opération de fusion crée un nouveau dictionnaire qui ne modifie en rien les dictionnaires d1
ou d2
et complètement indépendant d’eux :
d1 = {"paul" : 25, "jean" : 70, "thierry" : 40}
d2 = {"léa" : 15, "jean" : 50, "adam" : 1}
d = d1 | d2
print("d =", d)
d1["paul"] = 100
d["adam"] = 2
d["thierry"] = 100
print("d1 =", d1)
print("d2 =", d2)
print("d =", d)
d = {'paul': 25, 'jean': 50, 'thierry': 40, 'léa': 15, 'adam': 1}
d1 = {'paul': 100, 'jean': 70, 'thierry': 40}
d2 = {'léa': 15, 'jean': 50, 'adam': 1}
d = {'paul': 25, 'jean': 50, 'thierry': 100, 'léa': 15, 'adam': 2}
On notera que modifier d1
ou d2
ne modifie pas d
et qu’inversement, modifier d
laisse intacts d1
et d2
.
Il existe une version mutatrice de l’opération avec l’instruction |=
:
d1 = {"paul" : 25, "jean" : 70, "thierry" : 40}
d2 = {"léa" : 15, "jean" : 50, "adam" : 1}
d1 |= d2
print(d1)
{'paul': 25, 'jean': 50, 'thierry': 40, 'léa': 15, 'adam': 1}
En réalité, l’objet du membre de droite peut être un itérable de couples :
d = {"paul" : 25, "jean" : 70, "thierry" : 40}
L = [["léa", 15], ["jean", 50], ["adam", 1]]
d |= L
print(d)
{'paul': 25, 'jean': 50, 'thierry': 40, 'léa': 15, 'adam': 1}
Compléments¶
Ordre d’insertion des items d’un dictionnaire¶
Depuis la version 3.7 de python, un dictionnaire possède un ordre implicite qui est l’ordre d’insertion des items dans le dictionnaire. Cet ordre est celui d’apparition des items quand on affiche le dictionnaire. Par exemple, le code suivant :
d = {"paul" : 25, "jean" : 70, "thierry" : 40 }
print(d)
d["laure"]=25
print(d)
d["laure"]=30
d["hugo"]=49
print(d)
affiche
{'paul': 25, 'jean': 70, 'thierry': 40}
{'paul': 25, 'jean': 70, 'thierry': 40, 'laure': 25}
{'paul': 25, 'jean': 70, 'thierry': 40, 'laure': 30, 'hugo': 49}
ce qui montre que le premier élément entré dans d
est de clé "paul"
, l’avant-dernier est de clé "laure"
et le dernier est de clé "hugo"
.
Si un élément d’un dictionnaire est retiré et si ultérieurement, un item de même clé est replacé dans le dictionnaire, est-ce que le premier ordre d’insertion compte ? L’exemple qui suit illustre le comportement
1 2 3 4 5 6 7 8 9 10 11 | d = {"paul" : 25, "jean" : 70, "thierry" : 40 }
print(d)
del d["paul"]
d["laure"]=25
print(d)
d["paul"]=26
d["hugo"]=49
print(d)
|
12 13 14 | {'paul': 25, 'jean': 70, 'thierry': 40}
{'jean': 70, 'thierry': 40, 'laure': 25}
{'jean': 70, 'thierry': 40, 'laure': 25, 'paul': 26, 'hugo': 49}
|
- Ligne 4 : la clé
"paul"
est retirée du dictionnaire. - Ligne 9 : un item de clé
"paul"
est réintroduit. - Ligne 14 : l’ordre du nouvel item est postérieur à celui de tous les éléments présents l’ordre de sa réinsertion.
Autrement dit, lorsqu’une clé est supprimée, son ordre lors de la suppression n’a aucune influence sur son ordre après réinsertion.
Enfin, si une valeur est modifiée, cela ne modifie pas son ordre dans le dictionnaire :
1 2 3 4 5 6 | d = {"paul" : 25, "jean" : 70, "thierry" : 40 }
print(d)
d["laure"]=25
d["paul"]=26
print(d)
|
7 8 | {'paul': 25, 'jean': 70, 'thierry': 40}
{'paul': 26, 'jean': 70, 'thierry': 40, 'laure': 25}
|
- Ligne 5 : l’item de clé
"paul"
est modifié - Ligne 8 : son ordre dans le dictionnaire n’a pas changé pour autant.
Comment se voit l’ordre d’un dictionnaire ?
L’ordre d’un dictionnaire est l’ordre de traitement des items quand on parcourt le dictionnaire avec une boucle for
. C’est aussi l’ordre de placement des clés quand on convertit le dictionnaire en liste. Et c’est aussi l’ordre d’affichage :
d = {"paul" : 25, "jean" : 70, "thierry" : 40 }
print(d)
print("----------------")
for k in d:
print(k, d[k])
print("----------------")
print(list(d))
{'paul': 25, 'jean': 70, 'thierry': 40}
----------------
paul 25
jean 70
thierry 40
----------------
['paul', 'jean', 'thierry']
Copier un dictionnaire¶
Il y a deux façons équivalentes de procéder à une copie d’un dictionnaire d
:
- on utilise la méthode
copy
ded
- on appelle le constructeur
dict
surd
.
La méthode copy¶
1 2 3 4 5 6 7 8 9 10 11 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
dd = d.copy()
print(dd)
dd["jeanne"] = 20
dd["pascal"] = 42
del dd["paul"]
print()
print(d)
print(dd)
|
12 13 14 15 | {'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18}
{'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18}
{'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 20, 'pascal': 42}
|
- Ligne 3 :
dd
est une copie ded
. - Lignes 6-8 : si on modifie ou on ajoute une clé, ces modifications n’affectent pas le dictionnaire originel
d
(c’est en cela que c’est une copie).
Le constructeur dict¶
1 2 3 4 5 6 7 8 9 10 11 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
dd = dict(d)
print(dd)
dd["jeanne"] = 20
dd["pascal"] = 42
del dd["paul"]
print()
print(d)
print(dd)
|
12 13 14 15 | {'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18}
{'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18}
{'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 20, 'pascal': 42}
|
- Ligne 3 :
dd
est une copie ded
. - Ligne 6-8 : si on modifie ou on ajoute une clé de
dd
, ces modifications n’affectent pasd
.
Copie superficielle de dictionnaire¶
Toutefois, dans les deux cas (méthode copy
ou constructeur dict
), il se peut qu’une modification dans une copie affecte l’original.
Voici un exemple avec dict
:
1 2 3 4 5 6 7 8 9 10 | d = {"paul" : [25, 30, 35], "jean" : 70,
"thierry" : 40, "léa" : 15, "jeanne" : 18 }
dd = dict(d)
dd["paul"][0] = 42
print(d)
print()
print(dd)
|
11 12 13 | {'paul': [42, 30, 35], 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18}
{'paul': [42, 30, 35], 'jean': 70, 'thierry': 40, 'léa': 15, 'jeanne': 18}
|
- Ligne 6 : une valeur correspondant à une clé de la copie est modifiée
- Lignes 8 et 11 : cette modification affecte aussi l’original.
Plus précisément, si d
est le dictionnaire original et si dd
est une copie obtenue
- soit par
dd = d.copy()
- soit par
dd = dict(d)
alors si k
est une clé de d
et si d[k]
référence un objet mutable tel qu’une liste L
, toute modification du contenu de L via dd[k]
modifiera l’original d[k]
.
Il s’agit d’une copie superficielle suivant le même principe que la copie superficielle de liste.
Méthode pop
de dictionnaire¶
La méthode pop
d’un dictionnaire permet d’en supprimer une clé donnée :
d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
print(d)
d.pop("oslo")
print(d)
{'paris': 30, 'madrid': 35, 'oslo': 20}
{'paris': 30, 'madrid': 35}
L’intérêt de cette méthode est qu’un appel à pop
retourne la valeur de la clé avant suppression :
d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
print(d)
k="oslo"
print(f"Il faisait {d.pop(k)}° à {k}")
print(d)
{'paris': 30, 'madrid': 35, 'oslo': 20}
Il faisait 20° à oslo
{'paris': 30, 'madrid': 35}
Un autre avantage de pop
est qu’il est possible de donner une valeur de retour par défaut si la clé donnée n’est pas dans le dictionnaire :
d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
k="Albi"
print(f"Il faisait {d.pop(k, '40')}° à {k}")
Il faisait 40° à Albi
Supprimer un item avec popitem
¶
On peut supprimer d’un dictionnaire le dernier item inséré en appelant la méthode popitem
. Dans certaines situations, ces suppressions sont répétées. Dans le code ci-dessous, on vide un dictionnaire dans l’ordre inverse où les clés sont insérées (les dernières insérées sont les premières sorties) :
d = {"paul" : 25, "jean" : 70, "thierry" : 40 }
d["laure"]=25
d["laure"]=30
d["hugo"]=49
while d:
item=d.popitem()
print(item)
print(d)
('hugo', 49)
('laure', 30)
('thierry', 40)
('jean', 70)
('paul', 25)
{}
Un appel à popitem
sur un dictionnaire vide lève une exception de type KeyError
.
Vider un dictionnaire¶
On peut effacer le contenu d’un dictionnaire d
via la méthode clear
:
d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
print(d)
d.clear()
print(d)
{'paris': 30, 'madrid': 35, 'oslo': 20}
{}
L’intérêt de l’instruction d.clear()
au lieu de l’instruction d = {}
qui semble équivalente est que l’on modifie le dictionnaire initial au lieu d’en créer un nouveau si bien qu’on garde une référence vers le dictionnaire originel. Comparer les deux codes ci-dessous :
1 2 3 4 5 6 | d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
print(d)
dd = d
d.clear()
print(dd)
|
7 8 | {'paris': 30, 'madrid': 35, 'oslo': 20}
{}
|
- Lignes 1 et 2 :
d
etdd
référencent le même objet dictionnaire - Ligne 5 : le seul et unique dictionnaire est vidé
- Ligne 6 :
dd
référence le dictionnaire vidé.
1 2 3 4 5 6 | d = {"paris" : 30, "madrid" : 35, "oslo" : 20}
print(d)
dd = d
d = {}
print(dd)
|
7 8 | {'paris': 30, 'madrid': 35, 'oslo': 20}
{'paris': 30, 'madrid': 35, 'oslo': 20}
|
- Lignes 1 et 2 :
d
etdd
référencent le même objet dictionnaire - Ligne 5 : un nouveau dictionnaire (vide) est créé
- Ligne 6 :
dd
référence toujours le dictionnaire initial qui lui n’a pas été vidé.
Outils de création d’items¶
Pour créer des listes d’items que l’on passe ensuite au constructeur dict
, plusieurs techniques sont couramment employées dont les deux suivantes :
- les listes en compréhension,
- la fonction built-in
zip
.
Soit par exemple à créer un dictionnaire dont les clés soient les 8 chiffres romains :
"I"
, "V"
, "X"
, "L"
, "C"
, "D"
, "M"
et dont la valeur de chaque item soit la valeur numérique correspondant au chiffre romain, par exemple, la valeur de la clé "X"
est l’entier 10.
Fonction zip
¶
On peut créer notre dictionnaire en utilisant la fonction zip
:
romains="IVXLCDM"
valeurs=[1, 5, 10, 50, 100, 500, 1000]
d = dict(zip(romains, valeurs))
print(d)
{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
Liste en compréhension¶
On peut créer notre dictionnaire en utilisant une liste en compréhension :
romains="IVXLCDM"
valeurs=[1, 5, 10, 50, 100, 500, 1000]
d=dict([[romains[i],valeurs[i]] for i in range(len(romains))])
print(d)
{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
Cette façon de procéder en utilisant indice pour accéder aux objets des listes n’est pas considérée comme pythonique.
Création, fusion de dictionnaires par décompactage¶
L’exemple qui suit va permettre de mieux comprendre la possibilité de création de dictionnaire par décompactage de dictionnaires déjà créés :
1 2 3 4 5 6 | d1 = {"madrid" : 35, "oslo" : 20}
d2 = {"new-york" : 28, "madrid" : 40}
d = {"kiev":5, **d1, "odessa":12, **d2}
print(d)
|
7 | {'kiev': 5, 'madrid': 40, 'oslo': 20, 'odessa': 12, 'new-york': 28}
|
Depuis la version 3.5 de Python, il est possible de construire un dictionnaire en plaçant entre accolades et séparés par des virgules des items clé-valeur ainsi que des références à des dictionnaires à décompacter avec la syntaxe **
. Dans l’exemple ci-dessus, ligne 4, lors de la définiton de d
, on lit d1
qui est un dictionnaire et **d1
qui est du sucre syntaxique pour écrire à l’intérieur des accolades de d
la liste des items de d1
(c’est comme si on retirait les accolades de d1
). C’est pareil pour d2
. Noter qu’on peut incorporer dans d
des items hors dictionnaire, c’est le cas de par exemple de l’item "odessa":12
.
En cas de conflit de clés, par exemple la clé madrid
est présente dans d1
et d2
, c’est la dernière clé inséré dans le dictionnaire qui prime. Dans l’exemple ci-dessus, c’est l’item "madrid" : 40
qui sera retenu dans d
car cet item est dans d2
qui est placé après (à droite de) d1
.
Un cas particulier de ce qui précède est la fusion de deux dictionnaires d1
et d2
par d = {**d1, **d2}
.
Parcours d’un dictionnaire suivant les items, suivant les valeurs¶
Parcours suivant les items¶
On peut parcourir les items d’un dictionnaire avec la méthode items
:
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
for k, v in d.items():
print(k, "->", v)
paul -> 25
jean -> 70
thierry -> 40
léa -> 15
jeanne -> 18
La méthode dict.items
retourne un conteneur :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
dd=d.items()
print(len(dd))
5
Parcours suivant les valeurs¶
On peut parcourir les valeurs d’un dictionnaire avec la méthode values :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
for v in d.values():
print(v)
25
70
40
15
18
La méthode dict.values
retourne un conteneur qui contient des références vers les objets qui sont les valeurs du dictionnaire.
L’intérêt de la méthode values
est souvent limité car dans un dictionnaire, les valeurs seules, sans les clés, sont peu utilisables.
Listes des clés, des items, des valeurs d’un dictionnaire¶
On dispose d’un dictionnaire et on veut récupérer :
- une liste de clés
- une liste des items
- une liste des valeurs
Liste des clés¶
Pour récupérer une liste des clés, on utilise le constructeur list
sur le dictionnaire :
1 2 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(list(d))
|
3 | ['paul', 'jean', 'thierry', 'léa', 'jeanne']
|
- Ligne 2 : la liste des clés. Elles sont données dans le même ordre que l’ordre dans le dictionnaire littéral.
Liste des items¶
Pour récupérer une liste des items, il suffit d’appeler la méthode items
et de convertir le conteneur en liste :
methode_items.py
1 2 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(list(d.items()))
|
3 | [('paul', 25), ('jean', 70), ('thierry', 40), ('léa', 15), ('jeanne', 18)]
|
- Ligne 2 :
d.items()
est un conteneur des items ded
etlist
permet de récupérer sous forme de liste les éléments sur lesquels on itère, ici les items.
Les items sont fournis sous forme de tuple (clé, valeur) et non pas de liste.
L’usage de la méthode items
est à peu près équivalent à générer les items en parcourant le dictionnaire par ses clés :
items_cles.py
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
items = [(k, d[k]) for k in d]
print(items)
[('paul', 25), ('jean', 70), ('thierry', 40), ('léa', 15), ('jeanne', 18)]
Liste des valeurs¶
Pour récupérer une liste des valeurs, il suffit d’appeler la méthode values
et de convertir le conteneur en liste :
methode_values.py
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(list(d.values()))
[25, 70, 40, 15, 18]
L’usage de la méthode values
est à peu près équivalent à placer les valeurs dans une liste en compréhension en parcourant le dictionnaire par ses clés :
valeurs_cles.py
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
valeurs = [d[k] for k in d]
print(valeurs)
[25, 70, 40, 15, 18]
Noter qu’entre valeurs_cles.py
et methode_values.py
l’ordre des valeurs est le même.
Appeler dict
avec des arguments nommés¶
La syntaxe de dict
est typiquement illustrée par l’exemple suivant :
1 2 3 4 | mes_items = [["paul", 25], ["jeanne", 18],
["thierry", 45], ["jean", 70], ["léa", 15]]
d = dict(mes_items)
print(d)
|
5 | {'paul': 25, 'jeanne': 18, 'thierry': 45, 'jean': 70, 'léa': 15}
|
- ligne 3 :
dict
prend en argument une liste (lignes 1-2) formée de listes ayant chacune deux éléments.
Lorsque les clés sont des chaînes de caractères ayant une certaine forme (qui sera précisée ci-dessous), une autre syntaxe est possible :
1 2 3 4 | d= dict(paul = 25, jeanne = 18, thierry = 45, jean = 70, lea = 15)
print(d)
print(d["jeanne"])
|
5 6 | {'paul': 25, 'jeanne': 18, 'thierry': 45, 'jean': 70, 'lea': 15}
18
|
Détaillons cette syntaxe. Lorsque les clés d’un dictionnaire à construire sont toutes des chaînes de caractères pouvant représenter des noms de variables valides (ici, "paul"
, "jeanne"
, etc), il suffit de donner en argument à dict
(ligne 1) un argument nommé de la forme key = value
où key
est le contenu de la chaîne de caractères représentant une clé et value
est la valeur correspondant à cette chaîne (par exemple jean=70
).
Dans l’exemple ci-dessus, l’appel à dict
est référencé par d
. La chaîne "jeanne"
est une clé de d
car l’identificateur jeanne
est un argument nommé de l’appel de dict. La valeur de la clé "jeanne"
est fournie par la valeur affectée à l’argument nommé, ici 18.
Attention, ce procédé ne fonctionne que si les clés du dictionnaire à construire sont des chaînes de caractères ayant la syntaxe d’un identificateur. Ainsi, un dictionnaire contenant une clé valant 42 ne pourra être créé de la sorte.
Noter que l’ordre d’apparition des arguments nommés définit l’ordre d’insertion de ces éléments du dictionnaire.
Dictionnaire créé avec la méthode dict.fromkeys
¶
dict
est une classe, représentant le type dictionnaire. Cette classe possède une méthode qui permet de créer un dictionnaire à partir des deux éléments suivants :
- une liste de clés
- une même et unique valeur à donner à chacune des clés.
Cette méthode s’appelle fromkeys
.
Voici un exemple d’utilisation :
1 2 3 4 | keys = ["paul", "jeanne", "thierry", "jean", "léa"]
d = dict.fromkeys(keys, 42)
print(d)
|
5 | {'paul': 42, 'jeanne': 42, 'thierry': 42, 'jean': 42, 'léa': 42}
|
- Ligne 1 : les clés du futur dictionnaire
- Lignes 2 et 5 : chaque clé recevra pour valeur 42 ; noter que la méthode est appelée en suffixant la classe
dict
.
Dans le jargon de la POO Python, cette méthode est dite méthode de classe.
Absence de valeur¶
Si aucune valeur pour les clés n’est donnée, la valeur par défaut None
est attribuée à chaque clé :
1 2 3 4 | keys = ["paul", "jeanne", "thierry", "jean", "léa"]
d = dict.fromkeys(keys)
print(d)
|
5 | {'paul': None, 'jeanne': None, 'thierry': None, 'jean': None, 'léa': None}
|
- Ligne 2 : la méthode
dict.fromkeys
est appelée sans valeur à attribuer à chaque clé - Ligne 5 : chaque clé reçoit pour valeur
None
.
Itérable en argument de fromkeys
¶
Au lieu d’une liste, la méthode fromkeys
peut prendre n’importe quel itérable, par exemple une chaîne :
d=dict.fromkeys("aeiouy")
print(d)
{'a': None, 'e': None, 'i': None, 'o': None, 'u': None, 'y': None}
Syntaxes variées de l’argument itérable de dict
¶
Le type dict
peut être utilisé comme une fonction. L’idée est simple : pour créer un dictionnaire, dict
reçoit un itérable générant des éléments ayant DEUX composantes :
- une composante pour la clé
- une composante pour la valeur de la clé.
En pratique, pour créer un dictionnaire par cette méthode, de nombreuses variations de syntaxe sont possibles puisqu’il existe de nombreuses façons de créer un itérable générant deux éléments.
Exemple typique¶
Par exemple, voici deux façons équivalentes de définir un même dictionnaire :
1 2 3 4 5 6 7 | mes_items = [["paul", 25], ["thierry", 45], ["jean", 70], ["léa", 15]]
d = dict(mes_items)
print(d)
mes_items = ("paul", 25), ("thierry", 45), ["jean", 70], ["léa", 15]
d = dict(mes_items)
print(d)
|
8 9 | {'paul': 25, 'thierry': 45, 'jean': 70, 'léa': 15}
{'paul': 25, 'thierry': 45, 'jean': 70, 'léa': 15}
|
- Ligne 1 :
mes_items
est une liste de listes de deux éléments. - Ligne 5 :
mes_items
est un tuple de listes ou de tuples ayant chacun deux éléments.
dict
sans argument¶
Si dict
est appelé sans prendre d’argument, dict
renvoie un dictionnaire vide :
d = dict()
print(len(d))
0
Accès au premier élément d’un dictionnaire¶
Si un dictionnaire n’est pas vide, et qu’on ne connaît pas les clés du dictionnaire comment accéder à un élément du dictionnaire ? Pour y parvenir de façon économique et simple, il suffit de créer un itérateur sur le dictionnaire et d’itérer juste une seule fois :
d = {"léa" : 15, "paul" : 25, "jean" : 70, "thierry" : 40, "jeanne" : 18 }
if d:
it = iter(d)
print(next(it))
léa
L’item obtenu est le premier item inséré dans le dictionnaire.
La méthode get
¶
Si on essaie d’accéder à une valeur d’un dictionnaire d
avec une clé qui n’existe pas, on obtient un message d’erreur :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
print(d["pascal"])
KeyError: 'pascal'
Pour remédier à ce problème, on peut envisager de s’assurer avec un test de la présence de la clé avant de l’utiliser. En fait, on peut parvenir au même résultat avec la méthode get
mais sans prendre le risque de lever une exception :
1 2 3 4 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
v = d.get("pascal")
print(v)
|
5 | None
|
- Ligne 3 : si la chaîne
"pascal"
est une clé du dictionnaire,v
référencera la valeur associée à"pascal"
dans le dictionnaire, sinon,v
référenceraNone
.
Retour de la méthode get
¶
Par défaut, si d
est dictionnaire, l’appel d.get(my_key)
renvoie None
si my_key
n’est pas dans d
. Pour que la méthode get
puisse renvoyer une autre valeur v
que None
, il suffit de placer v
comme deuxième argument :
1 2 3 4 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18 }
v = d.get("pascal", 42)
print(v)
|
5 | 42
|
- Ligne 3 : la valeur par défaut, ici 42, est placé comme deuxième argument optionnel de l’appel à
get
. - Lignes 3 et 5 : la clé
"pascal"
est absente ded
, donc la méthodeget
renvoie42
.
Exemple d’utilisation de la méthode get
¶
Voici un cas d’usage de la méthode get
. On dispose d’une liste L
de nombres entiers et on souhaite créer un dictionnaire d
tel que
- chaque clé
k
soit une des valeurs distinctes deL
- la valeur de la clé
k
soit le nombre d’occurrences dek
dansL
.
Par exemple, si
L = [65, 9, 65, 9, 31, 31, 32, 65, 32, 31, 9, 31]
alors
d = {32: 2, 65: 3, 31: 4, 9: 3}
car, par exemple, 31 est présent 4 fois dans L
.
On peut coder le problème comme suit :
1 2 3 4 5 6 7 8 | def count(L):
d={}
for x in L:
d[x] =d.get(x, 0)+1
return d
L=[65, 31, 9, 32, 81]
print(count(L))
|
9 | {65: 1, 31: 1, 9: 1, 32: 1, 81: 1}
|
- Ligne 2 : au départ,
d
est vide. - Lignes 3-4 : lors du parcours de
L
, si une cléx
n’est pas présente dansd
alorsget(x, 0)
renvoie0
et doncd[x]
vaut1
ce qui est bien le nombre d’occurrence dex
à ce moment du parcours ded
. Si ultérieurement,x
apparaît à nouveau dansL
alorsd.get(x, 0)+1
vaudrad[x] + 1
ce qui est bien le nombre d’occurrences dex
dansd
à ce moment-là.
Ambiguïté possible de la méthode get
¶
Observons d’abord l’exemple suivant :
d1 = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15}
d2 = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "pascal" : None}
print(d1.get("pascal"), d2.get("pascal"))
None None
d1
ne contient pas la clé"pascal"
etget
renvoie doncNone
d2
contient la clé"pascal"
associée à la valeurNone
qui est donc renvoyée parget
.
La méthode get
d’un dictionnaire ne donne pas une information complète s’il retourne None
. En effet, si get
renvoie None
alors :
- ou bien la clé n’est pas dans le dictionnaire,
- ou bien la clé est dans le dictionnaire, associée à la valeur
None
.
Tuple comme clé d’un dictionnaire¶
Dans un dictionnaire littéral, si une clé est un tuple littéral, il faut placer des parenthèses autour du tuple. Ainsi, le code suivant est invalide :
d={1, "A": 42}
Il faut écrire
d={(1, "A"): 42}
print(d)
{(1, 'A'): 42}
Pour accéder à un élément du dictionnaire avec des crochets, la présence de parenthèses n’est pas obligatoire :
d={(1, "A"): 42}
print(d[1, "A"])
42
Attention qui si on fait appel à la méthode pop
, il faudra placer des parenthèses, sinon
1 2 3 4 5 6 | d={}
d[1, "A"] = 42
d.pop(1, "A")
print(d)
|
7 | {(1, 'A'): 42}
|
le code n’aura pas l’action souhaité et ne renverra aucune erreur :
- ligne 4 : comme 1 n’est pas clé du dictionnaire
d
, aucune clé n’est retirée, aucune exception n’est levée (c’est le principe de la méthodepop
) et l’expression renverra la valeur"A"
(sans l’utiliser).
La classe Counter
pour lister les doublons¶
Le module standard collections
dispose d’une structure de données appelée Counter et qui enregistre le nombre d’occurrences d’éléments d’un itérable. Voici un exemple :
from collections import Counter
L = [65, 9, 65, 9, 31, 31, 32, 42, 65, 32, 31, 50, 9, 31]
cnt = Counter(L)
print(cnt)
Counter({31: 4, 65: 3, 9: 3, 32: 2, 42: 1, 50: 1})
On observe que cnt
représente un dictionnaire dont les clés sont les éléments distincts de l’itérable L
et dont les valeurs correspondantes sont simplement le nombre d’occurrences dans l’itérable. Ainsi, on observe bien que dans L
, l’élément 31 apparaît 4 fois.
Une instance de la classe Counter
, comme cnt
ci-dessus, est un dictionnaire, et on peut donc utiliser les méthodes d’un dictionnaire, ce qui permet de facilement lister les doublons d’un itérable :
from collections import Counter
L = [65, 9, 65, 9, 31, 31, 32, 42, 65, 32, 31, 50, 9, 31]
cnt = Counter(L)
distrib=cnt.items()
doublons = [z for (z, k) in distrib if k >1]
print(doublons)
[65, 9, 31, 32]
Comme pour un dictionnaire, les éléments de l’itérable doivent être immutables.
Vues d’un dictionnaire¶
Conteneur adapté aux « composants » d’un dictionnaire¶
On appellera composant d’un dictionnaire :
- soit les clés,
- soit les valeurs des clés
- soit les items qui sont des paires clé et valeur.
La liste n’est pas forcément le conteneur pertinent pour placer les composants d’un dictionnaire.
Un dictionnaire possède justement des méthodes qui renvoient des conteneurs adaptés. Ces méthodes sont les suivantes :
keys
pour les clésvalues
pour les valeursitems
pour les items
Ces méthodes renvoient un objet appelé vue (view). Les vues permettent d’utiliser les mêmes opérations que les listes (l’appartenance, la longueur, l’itération) :
d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15, "jeanne" : 18}
v = d.values()
print(sum(v))
print(42 in v)
print(("léa", 15) in d.items())
print("pascal" in d.keys())
168
False
True
False
L’intérêt essentiel est que cela évite de créer des listes inutiles : par exemple, list(d.values())
ne permet pas de faire plus d’opérations pertinentes que ne le permet d.values()
.
Vues dynamiques sur un dictionnaire¶
La méthode keys
d’un dictionnaire d
est une vue dynamique sur les clés de d
, autrement dit, si les clés de d
sont modifiées par ajout ou suppression, d.keys()
mettra à jour les modifications sur les clés (suppression ou insertion) :
1 2 3 4 5 6 7 8 | d = {"Paul" : [25, 30, 35], "Jean" : 70, "Thierry" : 40, "Léa" : 15}
k = d.keys()
print(len(k))
d["Anouar"] = 42
del d["Jean"], d["Léa"]
print(len(k))
|
9 10 | 4
3
|
- Ligne 3 :
k
est une vue dynamique sur les clés ded
- Ligne 1 :
d
a présentement 4 clés - Ligne 6 : on ajoute une clé à
d
- Ligne 7 : on retire à
d
deux clés - Ligne 8 :
k
a bien mis à jour les clés puisque il répertorie \(4+1-2=3\) clés pourd
.
Le cas de values et items¶
Il en est de même des méthodes values
et items
:
d = {"Paul" : [25, 30, 35], "Jean" : 70, "Thierry" : 40, "Léa" : 15}
vals = d.values()
print(70 in vals)
del d["Jean"]
print(70 in vals)
print()
d["Thierry"] = 42
print(42 in vals)
True
False
True
Synchronisation des vues¶
Les trois vues (keys
, values
, items
) d’un dictionnaires sont synchronisées, c’est-à-dire que si on parcourt simultanément :
- les clés avec la méthode
keys
, - les valeurs avec la méthode
values
- les items par la méthode
items
,
à chaque étape, l’item sera bien constitué de la clé et de la valeurs :
d = {"Paul" : [25, 30, 35], "Jean" : 70, "Thierry" : 40, "Léa" : 15}
K = d.keys()
V = d.values()
I = d.items()
print(list(K))
print(list(V))
print(list(I))
['Paul', 'Jean', 'Thierry', 'Léa']
[[25, 30, 35], 70, 40, 15]
[('Paul', [25, 30, 35]), ('Jean', 70), ('Thierry', 40), ('Léa', 15)]
- Par vérification visuelle, on observe qu’à un indice donné
i
, l’item de la troisième liste est formé de la clé en indicei
de la première liste et de la valeur à l’indicei
de la deuxième liste.
Les dictionnaires par défaut (defaultdict
)¶
La bibliothèque standard de Python définit un type de dictionnaire appelé defaultdict
qui a toutes les propriétés d’un dictionnaire (un defaultdict
est un type, qui hérite du type dict
) et qui en diffère seulement lorsqu’on cherche à accéder à une clé du dictionnaire qui n’existe pas et pour laquelle, justement, le defautdict
fournit une valeur par défaut (d’où le terme de defaultdict
). Pour définir un defaultdict
, il faut l’importer depuis le module collections
:
1 2 3 4 5 6 7 8 9 10 11 | from collections import defaultdict
def f():
return 42
d = defaultdict(f)
d["begonia"] = 7
d["rose"] = 4
print(d)
print(d["prune"])
print(d)
|
12 13 14 | defaultdict(<function f at 0x7f22cec441f0>, {'begonia': 7, 'rose': 4})
42
defaultdict(<function f at 0x7f22cec441f0>, {'begonia': 7, 'rose': 4, 'prune': 42})
|
Lignes 3-6 : pour définir
defaultdict
, on a besoin d’une fonction qui va fournir les valeurs par défaut aux clés non présentes dans le dictionnaire. Ici la fonction est la fonctionf
constante et qui renvoie 42.Ligne 6 : pour l’instant
d
est vide.Lignes 7, 9 et 12 : on définit des clés du dictionnaire avec leur valeur, comme on le ferait pour un dictionnaire habituel en sorte que
d
possède désormais deux clés.Ligne 10 : on tente d’accéder à une clé de
d
qui n’existe pas, la clé"prune"
. Cet accès a pour conséquence :- d’appeler la fonction
f
et de renvoyerf()
(sans argument) pourd["prune"]
- de placer
"prune"
dans le dictionnaire avec pour valeur42
, cf. ligne 14.
- d’appeler la fonction
La fonction donnée en argument à defaultdict
s’appelle une default_factory
(fabrique de valeurs par défaut) et est un attribut du defaultdict
créé.
En cas de clé absente, la fonction defaultdict
, disons f
, ne reçoit aucun argument particulier, autrement dit est appelée sous la forme f()
.
Outre default_factory
, on peut donner des arguments nommés à default_factory
pour créer immédiatement des clés dans le dictionnaire, exactement comme on le ferait avec le constructeur dict
:
from collections import defaultdict
def f():
return 42
d = defaultdict(f, begonia = 7, rose = 4)
print(d.default_factory)
print(d)
<function f at 0x7f74381f41f0>
defaultdict(<function f at 0x7f74381f41f0>, {'begonia': 7, 'rose': 4})
Utilisation classique¶
Un exemple d’utilisation classique d’un defaultdict
est le suivant : on dispose d’une liste L
de nombres entiers et on souhaite créer un dictionnaire d
dont chaque clé k
soit une des valeurs distinctes de L
et dont la valeur soit le nombre d’occurrences de k
dans L
. Par exemple, si L = [65, 9, 65, 9, 31, 31, 32, 65, 32, 31, 9, 31]
alors d = {32: 2, 65: 3, 31: 4, 9: 3}
car, par exemple, 31
est présent 4 fois dans L
.
1 2 3 4 5 6 7 8 9 10 | from collections import defaultdict
def count(L):
d=defaultdict(int)
for x in L:
d[x] +=1
return d
L= [65, 9, 65, 9, 31, 31, 32, 65, 32, 31, 9, 31]
print(count(L))
|
11 | defaultdict(<class 'int'>, {65: 3, 9: 3, 31: 4, 32: 2})
|
- Ligne 4 : il est assez fréquent de donner comme
default_factory
un constructeur built-in commelist
,set
ou, comme ici,int
. L’important ici est queint()
vaille 0. - Ligne 6 : l’affectation
d[x] += 1
serait impossible avec undict
classique lorsquex
est une clé absente du dictionnaired
. Avec undefaultdict
, lorsquex
n’est pas présent dans le dictionnaire,d[x]
est donné pour la valeur par défautint()
, c’est-à-dire0
, et doncd[x] = d[x] + 1
donned[x] = 1
.
Noter que le code précédent est équivalent à :
from collections import defaultdict
def f():
return 0
d=defaultdict(f)
L= [65, 9, 65, 9, 31, 31, 32, 65, 32, 31, 9, 31]
for x in L:
d[x] +=1
print(d)
defaultdict(<function f at 0x7f187dd381f0>, {65: 3, 9: 3, 31: 4, 32: 2})
La méthode setdefault
¶
La méthode setdefault
permet de placer un item dans un dictionnaire d
si la clé proposée par setdefault
n’est pas déjà présente dans d
.
Voici un exemple d’utilisation de la méthode setdefault
:
1 2 3 4 5 6 7 8 9 10 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15}
v = d.setdefault("pascal", 42)
print(v)
print(d)
print()
v = d.setdefault("jeanne", 20)
print(v)
print(d)
|
11 12 13 14 15 | 42
{'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'pascal': 42}
20
{'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'pascal': 42, 'jeanne': 20}
|
- Ligne 3 : la chaîne
"pascal"
ne figure pas dansd
donc elle est ajoutée etsetdefault
renvoie la valeur donnée à cette clé, qui est celle présente en argument àsetdefault
. - Ligne 8 : la chaîne
"jeanne"
figure dansd
donc l’item n’est pas modifié etsetdefault
renvoie la valeur de la clé dansd
.
Autrement dit, lorsque la méthode setdefault
appliquée à un dictionnaire d
prend deux arguments k
et v
, elle crée l’item (k, v)
si la clé k
est absente de d
. Un appel à setdefault
renvoie la valeur de la clé présente dans le dictionnaire.
Absence de valeur¶
Si la méthode setdefault
appliquée à un dictionnaire d
ne prend qu’un seul argument et pas de valeur alors, si k
n’est pas une clé de d
, alors setdefault
ajoute k aux clés du dictionnaire et associe None
pour valeur de cette clé :
1 2 3 | d = {"paul" : 25, "jean" : 70, "thierry" : 40, "léa" : 15}
d.setdefault("pascal")
print(d)
|
4 | {'paul': 25, 'jean': 70, 'thierry': 40, 'léa': 15, 'pascal': None}
|
- Ligne 2 :
setdefault
ne prend ici qu’un seul argument, ici la chaîne"pascal"
- Lignes 3-4 : après appel,
"pascal"
est devenue une clé ded
et la valeur de la clé estNone
.
La méthode __missing__
¶
La méthode __missing__
permet d’ajouter des valeurs par défaut à des clés absentes :
1 2 3 4 5 6 7 8 9 10 | class D(dict):
def __missing__(self, k):
return "rosa"
d=D()
d[5]= "begonia"
d[10]= "kiwi"
print(d)
print(d[42])
print(d)
|
11 12 13 | {5: 'begonia', 10: 'kiwi'}
rosa
{5: 'begonia', 10: 'kiwi'}
|
- Lignes 5-8 : on crée un dictionnaire avec deux clés.
- Ligne 9 : on tente d’accéder à un clé inexistante.
- Ligne 9 : au lieu de lever une exception
KeyError
, la méthode__missing__
est appelée et le retour de l’appel donne une valeur. - Lignes 10 et 13 : toutefois, aucune clé
42
n’est rajoutée au dictionnaired
.
Comparer deux dictionnaires¶
L’opérateur ==
permet de comparer deux dictionnaires c’est-à-dire de savoir s’ils ont même contenu.
Deux dictionnaires sont considérés comme égaux s’ils admettent le même ensemble de clés et si les contenus correspondants de chaque clé sont égaux :
d = {42: [1, 2, 3], 10:10}
dd = {10:10, 42: [1, 2, 3]}
print(d == dd)
True
- les deux dictionnaires
d
etdd
sont égaux.
L’ordre d’insertion des clés n’a pas d’importance lorsqu’on compare deux dictionnaires.
Valeurs différentes¶
Si les clés sont identiques mais que les valeurs ne correspondent pas, les dictionnaires ne sont pas considérés comme égaux :
d = {42: [1, 2, 3], 10:10}
dd = {10:1000000, 42: [1, 2, 3]}
print(d == dd)
False
Clés différentes¶
Et si les clés ne sont pas identiques, les dictionnaires ne sont pas égaux :
d = {42: [1, 2, 3], 10:10}
dd = {10000:10, 42: [1, 2, 3]}
print(d == dd)
False
Modifier un dictionnaire pendant son parcours¶
On NE peut rajouter ou supprimer des items d’un dictionnaire d
lorsque celui-ci est parcouru par une boucle for
:
d={2020:42, 421:100, 512:1789, 0:42}
for k in d:
d[k+1]=0
Traceback (most recent call last):
File "_.py", line 3, in <module>
for k in d:
RuntimeError: dictionary changed size during iteration
Cela est dû à l’implémentation d’un dictionnaire : ajouter ou retirer des clés à un dictionnaire pourrait obliger à réimplémenter complètement le dictionnaire .
Itérateur sur un dictionnaire¶
Si d
est un dictionnaire, on obtient un itérateur sur d
en utilisant la fonction standard iter
:
d = {"léa" : 15, "paul" : 25, "jean" : 70, "thierry" : 40, "jeanne" : 18 }
it = iter(d)
print(list(it))
['léa', 'paul', 'jean', 'thierry', 'jeanne']
iter(d)
itère sur les clés du dictionnaire d
et, dans le même ordre, que lorsqu’on parcourt d
avec une boucle for
:
d = {"léa" : 15, "paul" : 25, "jean" : 70, "thierry" : 40, "jeanne" : 18 }
it = iter(d)
print(list(it))
for k in d:
print(k, end = " ")
print()
['léa', 'paul', 'jean', 'thierry', 'jeanne']
léa paul jean thierry jeanne
dict
et couple d’items non usuels¶
Quand on construit un dictionnaire d = dict(it)
, l’objet it
est un itérable sur des objets ayant exactement deux membres.
Par exemple, soit le fichier-texte à deux lignes suivant :
orange
kiwi
Voici un exemple peu réaliste mais possible d’items pour un dictionnaire :
1 2 3 4 | mes_items = ["42", open("my_file.txt"), set([42,2020])]
d = dict(mes_items)
print(d)
|
5 | {'4': '2', 'orange\n': 'kiwi\n', 42: 2020}
|
- Ligne 1 : la chaîne
"42"
est un itérable à deux membres - Ligne 1 :
open("my_file.txt")
est un itérable sur deux lignes de texte. La première ligne devient une clé ded
, la deuxième la valeur correspondante. - Ligne 1 :
set([42,2020])
est bien un itérable à deux éléments mais on ne sait pas à l’avance comment lequel des deux éléments sera le premier itérable et donc la clé. Donc ce type d’item est à éviter.
Signature de dict
et arguments nommés¶
L’usage d’arguments nommés avec le constructeur dict
:
d= dict(paul = 25, jeanne = 18, thierry = 45, jean = 70, léa = 15)
print(d)
print(d["jeanne"])
{'jean': 70, 'thierry': 45, 'léa': 15, 'paul': 25, 'jeanne': 18}
18
n’a rien de spécifique aux dictionnaires ; elle est simplement conforme à la signature du type dict
:
dict(it, **kwargs)
Les arguments nommés donnés à dict
comme sont paul = 25
dans l’exemple ci-dessus, sont compressés en le dictionnaire kwargs
.
Les dictionnaires de type OrderedDict
¶
La classe OrderedDict
est une classe dérivée de la class dict
qui permet de disposer, dès la version 3.1 de Python, de dictionnaires préservant l’ordre d’insertion des items :
from collections import OrderedDict
d = OrderedDict([('paul', 25), ('jean', 70), ('thierry', 40)])
print(d)
d["laure"]=25
print(d)
OrderedDict([('paul', 25), ('jean', 70), ('thierry', 40)])
OrderedDict([('paul', 25), ('jean', 70), ('thierry', 40), ('laure', 25)])
Depuis la version 3.7 de Python, les dictionnaires de type dict
possèdent cette propriété de respect de l’ordre d’insertion, ce qui retire de l’intérêt à la classe OrderedDict
. Toutefois, et suivant les arguments de Greg Gandenberger, ces dictionnaires gardent encore un intérêt pour les deux raisons suivantes :
- compatibilité avec du code antérieur à la version 3.7 de Python ;
- déclaration que l’ordre d’insertion des clés du dictionnaire est nécessaire au bon fonctionnement du programme. Et en effet, dans de nombreux programmes utilisant des dictionnaires, on n’a pas besoin de tenir compte de l’ordre d’insertion des clés. Utiliser un
OrderedDict
est justement une façon de déclarer que cet ordre a une importance.
Noter que la comparaison de dictionnaires de type OrderedDict
est plus stricte que celle des dict
:
from collections import OrderedDict
L = [('paul', 25), ('jean', 70)]
M = [('jean', 70), ('paul', 25)]
print(dict(L) == dict(M))
print(OrderedDict(L) == OrderedDict(M))
True
False