Les séquences
Rappel d'ouverture (5 minutes, cours fermé)
Réponds sans rouvrir les pages précédentes, en écrivant tes réponses.
- Écris la boucle qui parcourt une chaîne
motpar indice plutôt que par élément. - Avec
s = "chat", que se passe-t-il si on écrits[0] = "p"? - On écrit
a = [1, 2],b = a,b.append(3). Que vauta? Et si on avait écritb = a.copy()?
Corrigé
for i in range(len(mot)):puismot[i]dans le corps. Les indices vont de0àlen(mot) - 1, etranges'arrête juste avant sa borne : les deux tombent juste ensemble.- Une
TypeError: une chaîne est immuable, on ne peut pas en changer un caractère. avaut[1, 2, 3]: les deux noms désignent le même objet. Aveccopy(),avaudrait[1, 2], car on aurait fabriqué une seconde liste.
Ces trois questions portent sur trois types différents. Toute la page d'aujourd'hui consiste à voir ce qui est commun aux trois, et la seule chose qui les sépare vraiment.
Tu connais maintenant trois façons de ranger plusieurs valeurs dans une seule variable : la chaîne de caractères, la liste et le tuple. Tu les as apprises séparément, et tu as pu avoir l'impression d'apprendre trois choses.
C'est faux : tu en as appris une seule, déclinée trois fois. Cette page le montre, et c'est la raison pour laquelle la suite du cours te coûtera moins cher que le début.
Séquence
Une séquence est une suite d'éléments ordonnés, numérotés de 0 à len(seq) - 1, auxquels on accède par leur indice.
En Python, les séquences sont les chaînes (str), les listes (list) et les tuples (tuple).
Ce qui est commun aux trois
Aucune de ces écritures ne dépend du type. Recopie ce tableau, c'est le résumé de trois pages de cours.
| Ce qu'on veut | Écriture | str |
list |
tuple |
|---|---|---|---|---|
l'élément numéro i |
seq[i] |
oui | oui | oui |
| le dernier élément | seq[-1] |
oui | oui | oui |
| le nombre d'éléments | len(seq) |
oui | oui | oui |
savoir si x s'y trouve |
x in seq |
oui | oui | oui |
| tout parcourir | for e in seq: |
oui | oui | oui |
| parcourir par indice | for i in range(len(seq)): |
oui | oui | oui |
| coller deux séquences | seq1 + seq2 |
oui | oui | oui |
| répéter | seq * n |
oui | oui | oui |
| en extraire un morceau | seq[debut:fin] |
oui | oui | oui |
mot = "Pikachu"
tokens = ["Une", "suite", "d'éléments"]
point = (5, 0, 12)
print(mot[0], tokens[0], point[0]) # P Une 5
print(len(mot), len(tokens), len(point)) # 7 3 3
print("k" in mot, "suite" in tokens, 0 in point) # True True True
C'est ce qui rend la suite plus facile
Chaque fois que tu rencontreras une nouvelle structure de données, commence par te demander : est-ce une séquence ? Si oui, tu sais déjà y accéder, la mesurer et la parcourir, sans rien apprendre de neuf. Il ne te restera à apprendre que ce qui lui est propre.
Ce qui les sépare : la mutabilité
C'est la seule différence de fond entre les trois, et c'est celle qui produit le plus de bugs.
Mutable, immuable
- Une séquence immuable ne peut pas être changée après sa création. Toute opération qui semble la modifier fabrique en réalité une nouvelle séquence. Les chaînes et les tuples sont immuables.
- Une séquence mutable peut être changée sur place, sans en fabriquer une nouvelle. Les listes sont mutables.
L'analogie, et sa limite
Un tuple ou une chaîne, c'est un texte écrit au stylo : pour corriger, il faut tout réécrire. Une liste, c'est un texte écrit au crayon : on gomme et on modifie sur place.
La limite de l'image : au crayon comme au stylo, il n'y a qu'un seul exemplaire du texte. Si deux noms désignent la même liste, la gommer par l'un la gomme pour l'autre. C'est tout le problème ci-dessous.
Ce qui se passe en mémoire
C'est le point à comprendre, pas à retenir. Une variable ne contient pas la séquence : elle pointe vers elle.
Cas immuable. On ne peut rien changer, donc la seule façon d'obtenir une valeur différente est d'en fabriquer une nouvelle et de faire pointer le nom dessus. L'ancienne est abandonnée.
Cas mutable. On peut changer l'objet lui-même. Le nom continue de pointer au même endroit, mais ce qui s'y trouve a changé.
La conséquence, qui est la source de bugs numéro un
Si deux noms pointent vers la même séquence mutable, modifier par l'un se voit par l'autre. Tu l'as vu sur les listes avec b = a.
Sur une séquence immuable, ce piège n'existe pas : puisqu'on ne peut rien modifier sur place, personne ne peut te changer ta chaîne dans le dos.
a = [1, 2]; b = a; b.append(3)
print(a) # [1, 2, 3] la liste est partagée
x = "ab"; y = x; y = y + "c"
print(x) # "ab" impossible de toucher à x
C'est pour cela qu'on choisit un tuple plutôt qu'une liste quand une donnée ne doit pas changer : ce n'est pas une coquetterie, c'est une protection.
Prédire avant d'exécuter
Sans lancer le code, écris ce qui s'affiche, puis vérifie.
Réponse
[99, 2, 3] [99, 2, 3] [1, 2, 3] puis abc ABC.
Dans le premier cas, b et a sont un seul objet, c en est une copie faite avant la modification. Dans le second, upper() ne modifie pas s : elle ne peut pas, une chaîne est immuable. Elle en renvoie une nouvelle, qu'on range dans t.
Extraire un morceau : le slice
L'écriture seq[debut:fin] renvoie la portion allant de debut inclus à fin exclus. C'est le même intervalle semi-ouvert que range, pour la même raison : la longueur du morceau vaut exactement fin - debut.
t = ("A", "B", "C", "D", "E", "F")
print(t[1:4]) # ('B', 'C', 'D') de 1 à 3
print("ABCDEF"[3:]) # 'DEF' de 3 à la fin
print([0,1,2,3][:2])# [0, 1] du début à 1
Le slice renvoie toujours une nouvelle séquence, du même type que l'originale. C'est d'ailleurs une façon courante de copier une liste : b = a[:].
Une séquence peut en contenir une autre : l'imbrication
Ce n'est pas une fonctionnalité de plus, c'est une conséquence de ce qui précède. Si tokens[4] vaut "suite", alors tokens[4] est une chaîne, donc une séquence, donc on peut encore lui mettre des crochets : tokens[4][2] vaut "i".
C'est ce qui permettra de représenter un tableau de données : une liste de lignes, chaque ligne étant elle-même une liste. Tu t'en serviras dans le thème des données en tables.
Imbrication, exercice guidé
- Combien d'éléments a
mots? - Quel est le type complet de
mots? - Affiche le
"u"de"jour". - Affiche le
"m"de"mi". - Affiche le
"u"de"chou". - Affiche la chaîne
"chouette"en concaténant deux éléments demots.
Indice
Lis les crochets de gauche à droite, un par un. mots[0] est une liste. mots[0][1] est une chaîne. mots[0][1][2] est un caractère. À chaque crochet, on descend d'un niveau.
Solutions
- 3 éléments (trois listes internes).
list[list[str]], une liste de listes de chaînes.print(mots[0][1][2])print(mots[1][0][0])print(mots[2][0][3])print(mots[2][0] + mots[2][1])
Imbrication, exercice autonome
- Combien d'éléments a
struct? - Quel est le type complet de
struct? - Affiche
"root", puis"J", puis"N", puis le"o"de"Mot".
Solutions
- 4 éléments.
tuple[str, tuple[str, list[str], str], tuple[str, tuple[int, int], str], tuple[str, tuple[int, str], list[str]]]struct[0],struct[2][2],struct[3][2][0],struct[3][1][1][1].
Deux pièges à connaître par leur nom
Piège : confondre l'indice et la valeur
L'indice dit où est l'élément, la valeur dit ce qu'il est. Les crochets attendent un indice, l'opérateur in une valeur.
notes = [15, 12, 18]
print(notes[15]) # IndexError : 15 est une valeur, pas une position
print(notes[0]) # 15 : accès par indice
print(15 in notes) # True : test sur une valeur
C'est une confusion classique et tenace, y compris chez des élèves qui savent par ailleurs très bien programmer. Quand une IndexError te surprend, pose-toi d'abord cette question-là.
Piège : utiliser len(seq) comme indice
Les indices vont de 0 à len(seq) - 1. L'indice len(seq) n'existe donc jamais.
lst = [10, 20, 30]
print(lst[len(lst)]) # IndexError
print(lst[len(lst) - 1]) # 30, le dernier
print(lst[-1]) # 30, plus lisible
C'est la même erreur, décalée d'un cran, que celle qu'on fait en écrivant range(1, n) quand on voulait aller jusqu'à n. Le décalage de un est l'erreur la plus fréquente de toute la programmation, et elle ne se corrige pas en faisant attention : elle se corrige en traçant le premier et le dernier tour.
Résumé
| Type | Exemple | Mutable ? | Quand l'utiliser |
|---|---|---|---|
str |
"Hello" |
non | du texte |
tuple |
(1, 2, 3) |
non | des données qui ne doivent pas changer, un retour multiple |
list |
[1, 2, 3] |
oui | des données qu'on construit ou qu'on modifie |
À retenir, et c'est tout :
- les indices vont de
0àlen(seq) - 1; - l'accès, la longueur, l'appartenance, le parcours et le slice s'écrivent pareil pour les trois ;
- seule la liste est modifiable sur place, ce qui fait sa puissance et son seul danger.
Et la suite ?
La structure que tu vas rencontrer juste après, le dictionnaire, n'est pas une séquence. Il n'a pas d'indices, et on n'y accède pas par une position mais par une clé. C'est le premier objet du cours auquel ce tableau ne s'applique pas, et c'est pour cela qu'il faut d'abord bien tenir ce qui précède.
Pour s'entraîner
Les exercices sur les séquences, ainsi que les fonctions à écrire, se trouvent sur la page Exercices sur les séquences.