Aller au contenu principal

Les structures de données

Une variable retient une valeur. Que fait-on quand on en as trente ?

Écrire note1, note2, note3 jusqu'à note30 est possible, et c'est une impasse : impossible d'en faire la moyenne sans écrire trente additions, impossible d'en ajouter une trente et unième sans modifier le programme. Il faut ranger plusieurs valeurs sous un seul nom. C'est l'objet de ce chapitre.

Ce qu'on va apprendre

  • Ranger une suite de valeurs dans une liste, et la parcourir.
  • Associer une valeur à une clé dans un dictionnaire.
  • Manipuler du texte comme une suite de caractères.
  • Comprendre pourquoi deux étiquettes sur la même liste posent un problème.
Chaque bloc part d'un Python neuf
Les blocs de cette page ne se souviennent pas les uns des autres : les données sont redéfinies à chaque fois. On peut donc modifier et relancer n'importe lequel sans casser les suivants.

La liste : une suite de valeurs

Définition

Une liste est une collection ordonnée et modifiable de valeurs, écrite entre crochets. Elle peut contenir des nombres, du texte, ou un mélange.

main.py
Sortie
>_ Prêt à exécuter…

Trois valeurs suffisent à voir l'intérêt : la moyenne s'écrit pareil pour quatre notes ou pour trois mille.

Atteindre un élément

Chaque élément a un indice, et les indices commencent à zéro.

main.py
Sortie
>_ Prêt à exécuter…
Trois éléments, indices 0, 1 et 2
Le décalage entre « le premier » et l'indice 0 est la source d'erreur numéro un sur les listes. Une liste de trois éléments a pour dernier indice 2, pas 3.
La règle : le dernier indice vaut toujours len(liste) - 1. Ou plus simplement, liste[-1], qui compte depuis la fin et ne se trompe jamais.

Le bloc suivant est faux exprès.

main.py
Sortie
>_ Prêt à exécuter…

IndexError: list index out of range. Il y a bien trois notes, mais leurs indices sont 0, 1 et 2. Remplacer 3 par 2, ou mieux par -1, et relance.

Modifier

Une liste est mutable : on peut changer, ajouter, retirer après coup.

main.py
Sortie
>_ Prêt à exécuter…

append, remove, pop et len suffisent pour presque tout. Il en existe d'autres, insert pour placer à un endroit précis, extend pour ajouter plusieurs éléments, clear pour vider : on les chercheras le jour où on en auras besoin.

Parcourir et calculer

main.py
Sortie
>_ Prêt à exécuter…
Compter, cumuler, filtrer
Le schéma revient partout : on prépare un résultat vide avant la boucle, on le fait grandir dans la boucle, on l'utilise après.
main.py
Sortie
>_ Prêt à exécuter…

Le dictionnaire : associer une clé à une valeur

Une liste de notes ne dit pas à qui appartient chaque note. On serait tenté de tenir deux listes en parallèle, prenoms et notes, et de les faire correspondre par leur indice. C'est fragile : il suffit d'en trier une pour tout casser.

Définition

Un dictionnaire associe des clés à des valeurs, entre accolades, avec la syntaxe cle: valeur. On accède à une valeur par sa clé, jamais par un indice.

main.py
Sortie
>_ Prêt à exécuter…
Liste ou dictionnaire : la question à se poser
Comment vais-je retrouver une valeur ?
Par sa position, ou parce que je veux toutes les parcourir dans l'ordre : c'est une liste.
Par un identifiant, un nom, un code, une date : c'est un dictionnaire.
Le symptôme qui doit alerter : deux listes de même longueur qu'on parcourt avec le même indice. Cela veut presque toujours dire qu'un dictionnaire s'imposait.

Parcourir un dictionnaire

main.py
Sortie
>_ Prêt à exécuter…

.keys() donne les clés, .values() les valeurs, .items() les paires. C'est .items() qu'on emploiera le plus.

La clé qui n'existe pas

main.py
Sortie
>_ Prêt à exécuter…

KeyError: 'Zoe'. Demander une clé absente arrête le programme. Quand on n'est pas sûr, .get() répond sans planter, et accepte une valeur de repli.

main.py
Sortie
>_ Prêt à exécuter…
None n'est pas zéro
notes.get("Zoe") renvoie None, qui signifie « rien », et non 0, qui est une note. Calculer avec None déclenche un TypeError.
Quand la valeur sert à un calcul, donne toujours un repli : notes.get("Zoe", 0). Quand on veut savoir si la clé existe, emploie in, c'est plus clair qu'une comparaison à None.

Le texte, une suite de caractères

Une chaîne se comporte presque comme une liste de lettres, à une différence près : elle est immuable. On peut la lire et en fabriquer de nouvelles, jamais changer une lettre en place.

main.py
Sortie
>_ Prêt à exécuter…

Les deux méthodes qui servent le plus font le lien avec les listes : split() découpe un texte en liste, join() recolle une liste en texte.

main.py
Sortie
>_ Prêt à exécuter…
Immuable, donc pas de méthode qui modifie
mot.upper() ne change pas mot : il renvoie une nouvelle chaîne. Si on veut garder le résultat, il faut le ranger : mot = mot.upper().
C'est l'inverse des listes, où notes.append(15) modifie la liste elle-même et ne renvoie rien. Cette différence explique la moitié des surprises du chapitre.

Un tuple est une liste immuable, écrite entre parenthèses : coordonnees = (3, 7). On l'emploie pour des données fixes, et l'on peut le dépaqueter d'un coup avec x, y = coordonnees.

Deux étiquettes, une seule liste

Souvenons-nous du chapitre 2 : une variable est une étiquette collée sur une valeur. Avec les listes, cette image cesse d'être une commodité et devient indispensable.

main.py
Sortie
>_ Prêt à exécuter…
L'affectation ne copie pas
equipe_b = equipe_a ne fabrique pas une deuxième liste : elle colle une deuxième étiquette sur la liste existante. Modifier la liste par l'une ou par l'autre revient au même, puisqu'il n'y en a qu'une.
Pour obtenir une vraie copie indépendante : equipe_b = equipe_a.copy().
Ce comportement ne vaut que pour les valeurs modifiables, listes et dictionnaires. Avec un nombre ou un texte, la question ne se pose pas : on ne peut pas les modifier, donc on ne peut pas surprendre l'autre étiquette.

Le dessin ci-dessous montre la situation telle qu'elle est en mémoire, et non telle qu'on l'imagine. Il n'y a qu'une seule liste, et deux étiquettes pointées dessus.

les nomsles objets en mémoireequipe_aequipe_beffectif["Alice", "Bob", "Charlie"]modifiable42immuable
Après equipe_b = equipe_a puis equipe_b.append("Charlie"). Les deux noms désignent le même objet, et c'est pourquoi la modification se voit des deux côtés.

L'image de la boîte, qui suffit tant qu'on manipule des nombres, cesse ici de fonctionner. Une variable n'est pas une boîte qui contient une valeur : c'est une étiquette collée sur un objet. Deux étiquettes sur le même objet, c'est une situation ordinaire, et la seule façon de la comprendre est de la voir.

main.py
Sortie
>_ Prêt à exécuter…

Compter les occurrences est l'exercice type du dictionnaire, et celui où l'on se perd le plus vite : la clé change à chaque tour, et la valeur associée aussi. La trace le déroule.

pas 1 / 26
1compte = {}
2for c in "abraca":
3 if c in compte:
4 compte[c] = compte[c] + 1
5 else:
6 compte[c] = 1
7print(compte)

Variables

compte{}
Le programme, exécuté pas à pas. La ligne surlignée est celle qui vient de s'exécuter, et le tableau de droite donne l'état des variables juste après.

Le if c in compte distingue les deux seuls cas possibles : la lettre a déjà été vue, et il faut incrémenter ; ou c'est la première fois, et il faut créer l'entrée à 1. Oublier le else donne une erreur de clé absente au tout premier caractère, ce que la trace montre en s'arrêtant net.

Quelle structure choisir

StructureÉcritureModifiableQuand l'employer
Liste[1, 2, 3]ouiune suite de valeurs, parcourue ou indexée
Dictionnaire{"a": 1}ouiretrouver une valeur par un identifiant
Chaîne"texte"nondu texte
Tuple(3, 7)nondes valeurs fixes qui vont ensemble
Vérification rapideon peut se reprendre

1.Quel est le dernier indice valide d'une liste de 5 éléments ?

2.Après a = [1, 2], b = a, b.append(3), que vaut a ?

3.Que renvoie {"x": 1}.get("y") ?

4.Que fait mot.upper() sur mot = "python" ?

5.Deux listes prenoms et notes de même longueur, parcourues avec le même indice. Que faudrait-il ?

À mettre en pratique

Le bulletin. À partir du dictionnaire ci-dessous, affiche la moyenne générale avec deux décimales, puis le nom du meilleur élève.

main.py
Sortie
>_ Prêt à exécuter…
Afficher la solution
main.py
Sortie
>_ Prêt à exécuter…

La moyenne se lit directement sur les valeurs : sum(notes.values()).

Pour le maximum, on emploie le schéma « préparer, parcourir, comparer » : une variable qui retient le meilleur trouvé jusqu'ici, initialisée à une valeur que tout dépassera. Python offre un raccourci, max(notes, key=notes.get), mais la boucle explicite est ce qu'on attend de soi en devoir, et c'est elle qui se transpose à tous les autres problèmes.

Synthèse

  • Une liste range une suite de valeurs ; ses indices commencent à zéro.
  • Le dernier indice vaut len(liste) - 1, ou plus sûrement liste[-1].
  • append, remove, pop et len couvrent l'essentiel.
  • Un dictionnaire retrouve une valeur par une clé ; .items() parcourt les paires.
  • Une clé absente déclenche un KeyError : emploie .get(cle, repli) ou le test in.
  • None signifie « rien », ce n'est pas 0.
  • Une chaîne est immuable : ses méthodes renvoient une nouvelle chaîne.
  • b = a sur une liste ne copie pas : les deux étiquettes désignent la même liste. Il faut .copy().
  • Deux listes parallèles parcourues par le même indice : c'est un dictionnaire qu'il fallait.

Mettre en pratique