Dictionnaires

\(\newcommand{\ds}{\displaystyle}\) \(\newcommand{\Frac}{\ds\frac}\) \(\renewcommand{\r}{\mathbb{ R}}\) \(\newcommand{\C}{\mathbb{ C}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\z}{\mathbb{ Z}}\) \(\newcommand{\Q}{\mathbb{ Q}}\) \(\newcommand{\N}{\mathbb{ N}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\ol}{\overline}\) \(\newcommand{\abs}[1]{\left| \,{#1} \right|}\) \(\newcommand{\pv}{\;;\;}\) \(\newcommand{\ens}[1]{\left\{ {#1} \right\}}\) \(\newcommand{\mens}[1]{\setminus\left\{ {#1} \right\}}\) \(\newcommand{\Par}[1]{\left({#1}\right)}\) \(\newcommand{\pe}[1]{\left\lfloor {#1} \right\rfloor}\) \(\newcommand{\trans}[1]{\,^t\!{#1}}\)

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 de jeanne

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 variable age.

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 dictionnaire d.

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 est valeurs[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 dictionnaire d.
  • 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 variable oslo ; de même, la valeur de la clé dans le dictionnaire est référencée par la variable temp_oslo.
  • Lignes 7 et 11 : la clé "oslo" de d 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 dans d1 et d2, la valeur de k sera remplacée dans d1 par la valeur de k dans d2
  • si k est une clé de d2 non présente dans d1, alors k deviendra une nouvelle clé de d1 et la valeur de k sera la valeur de k dans d2.

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 de d
  • on appelle le constructeur dict sur d.

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 de d.
  • 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 de d.
  • Ligne 6-8 : si on modifie ou on ajoute une clé de dd, ces modifications n’affectent pas d.

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 et dd 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 et dd 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 de d et list 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 = valuekey 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érencera None.

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 de d, donc la méthode get renvoie 42.

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 de L
  • la valeur de la clé k 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.

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 dans d alors get(x, 0) renvoie 0 et donc d[x] vaut 1 ce qui est bien le nombre d’occurrence de x à ce moment du parcours de d. Si ultérieurement, x apparaît à nouveau dans L alors d.get(x, 0)+1 vaudra d[x] + 1 ce qui est bien le nombre d’occurrences de x dans d à 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" et get renvoie donc None
  • d2 contient la clé "pascal" associée à la valeur None qui est donc renvoyée par get.

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éthode pop) 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és
  • values pour les valeurs
  • items 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 de d
  • 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 pour d.

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 indice i de la première liste et de la valeur à l’indice i 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 fonction f 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 renvoyer f() (sans argument) pour d["prune"]
    • de placer "prune" dans le dictionnaire avec pour valeur 42, cf. ligne 14.

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 comme list, set ou, comme ici, int. L’important ici est que int() vaille 0.
  • Ligne 6 : l’affectation d[x] += 1 serait impossible avec un dict classique lorsque x est une clé absente du dictionnaire d. Avec un defaultdict, lorsque x n’est pas présent dans le dictionnaire, d[x] est donné pour la valeur par défaut int(), c’est-à-dire 0, et donc d[x] = d[x] + 1 donne d[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 dans d donc elle est ajoutée et setdefault renvoie la valeur donnée à cette clé, qui est celle présente en argument à setdefault.
  • Ligne 8 : la chaîne "jeanne" figure dans d donc l’item n’est pas modifié et setdefault renvoie la valeur de la clé dans d.

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é de d et la valeur de la clé est None.

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 dictionnaire d.

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 et dd 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é de d, 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