Aller au contenu
\( \definecolor{colordef}{RGB}{249,49,84} \definecolor{colorprop}{RGB}{18,102,241} \)
Éducation Nationale Française · Première Spécialité

Python : chaînes et listes

⌚ ~130 min ▢ 14 blocs ✓ 79 exercices PDFCours complet PDFFiche d'exercices PDFCorrections ➣ Prérequis : Python : fonctions
I Le texte comme séquence
Jusqu'ici une chaîne était un bloc : affichée, collée à une autre, comparée. C'est aussi une séquence : une rangée de caractères, chacun à une position numérotée, et Python compte les positions à partir de 0. Avec les positions viennent de nouvelles questions : quel est le troisième caractère, combien y en a-t-il, cette lettre apparaît-elle, à quoi ressemble le texte à l'envers. Traiter le texte comme une séquence, c'est parcourir une chaîne caractère par caractère, y compter, et en construire une nouvelle.
Signaler une erreur
Définition — Positions et longueur
Dans une chaîne s, les caractères sont numérotés à partir de 0 : s[0] est le premier caractère, s[1] le deuxième, et ainsi de suite. Le nombre de caractères est len(s), donc, quand s n'est pas vide, le dernier caractère est s[len(s) - 1], aussi écrit s[-1]. Une position égale à len(s) ou au-delà n'existe pas : l'utiliser arrête le programme avec une IndexError. Une espace et un signe de ponctuation sont des caractères comme les autres.
Exemple
Qu'affiche le programme suivant ?
word = "Python"
print(word[0])
print(word[5])
print(len(word))
print(word[len(word) - 1])
print(word[-2])

P
n
6
n
o
Six caractères, positions 0 à 5 : le sixième caractère est à la position 5. len(word) - 1 vaut 5, la dernière position ; -2 compte depuis la fin et donne l'avant-dernier. Un caractère pris dans une chaîne est lui-même une chaîne d'un caractère.

Définition — Parcourir une chaîne
La boucle for ch in s: exécute son bloc une fois par caractère de s, du premier au dernier, ch contenant le caractère courant : c'est le parcours par caractère. La boucle for i in range(len(s)): s'exécute une fois par position, avec i de 0 à len(s) - 1 et le caractère disponible sous s[i] : c'est le parcours par position, utilisé quand la position elle-même compte.
Définition — Tranches et opérations
La tranche s[a:b] est la chaîne faite des caractères de la position a comprise à la position b exclue ; s[:b] part du début et s[a:] va jusqu'à la fin. L'opérateur + colle deux chaînes, * en répète une, c in s vaut True quand la chaîne c apparaît dans s, et c not in s est le contraire. Deux chaînes se comparent avec ==, < et > caractère par caractère, selon les codes des caractères, ce qui pour les minuscules est l'ordre de l'alphabet.
Définition — Les méthodes d'une chaîne
Une méthode est une fonction attachée à une valeur, appelée avec un point : s.upper() rend s en majuscules, s.lower() en minuscules, s.count(c) le nombre de fois où le caractère c apparaît, s.replace(a, b) une copie où chaque a est devenu b. Une chaîne est immuable : aucune méthode et aucune affectation ne change ses caractères ; une méthode rend une nouvelle chaîne, qu'il faut ranger pour la garder.
Exemple
Qu'affiche le programme suivant ?
s = "hello world"
print(s[0:5])
print(s[6:])
print("wor" in s)
print(s.upper())
print(s.count("o"))
print(s)

hello
world
True
HELLO WORLD
2
hello world
La tranche s[0:5] prend les positions 0 à 4 : cinq caractères, l'espace en position 5 exclue. La dernière ligne montre que s n'a pas changé : upper a rendu une nouvelle chaîne, affichée puis oubliée.

Méthode — Parcourir et compter
  1. Créer le compteur à 0 avant la boucle.
  2. Parcourir la chaîne avec for ch in s:.
  3. Tester le caractère courant avec un if : est-ce celui à compter ? Pour plusieurs caractères à la fois, utiliser ch in "aeiou".
  4. Compter dans le if, et rendre le compteur après la boucle.
Exemple
Quelle est la valeur de chaque appel ?
def count_vowels(s):
    count = 0
    for ch in s:
        if ch in "aeiou":
            count = count + 1
    return count
>>> count_vowels("banana")
>>> count_vowels("sky")
>>> count_vowels("Ada Lovelace")

>>> count_vowels("banana")
3
>>> count_vowels("sky")
0
>>> count_vowels("Ada Lovelace")
5
Le parcours par caractère avec un compteur. Le troisième appel compte a, o, e, a, e : le A majuscule n'est pas dans "aeiou", donc il n'est pas compté. Pour le compter aussi, tester ch.lower() in "aeiou".

Méthode — Construire une nouvelle chaîne
  1. Partir de la chaîne vide : result = "".
  2. Parcourir la chaîne de départ caractère par caractère.
  3. Ajouter au résultat les caractères à garder, transformés si besoin : result = result + ch ajoute à la fin, result = ch + result ajoute au début.
  4. Rendre le résultat après la boucle. La chaîne de départ est intacte.
Exemple
Quelle est la valeur de chaque appel ?
def reverse(s):
    result = ""
    for ch in s:
        result = ch + result
    return result

def is_palindrome(s):
    return s == reverse(s)
>>> reverse("abc")
>>> is_palindrome("kayak")
>>> is_palindrome("Kayak")

>>> reverse("abc")
'cba'
>>> is_palindrome("kayak")
True
>>> is_palindrome("Kayak")
False
Chaque caractère est mis devant le résultat construit jusque-là : "a", puis "ba", puis "cba". Un palindrome se lit de même à l'envers ; la comparaison est exacte, et une majuscule diffère d'une minuscule, d'où la troisième valeur. Comparer s.lower() avec son inverse l'accepterait.

Définition — Les lettres comme nombres
Chaque caractère a un numéro, son code : ord(c) donne le code du caractère c, et chr(n) donne le caractère de code n. Les minuscules a à z ont des codes consécutifs, de 97 à 122, et de même les majuscules, de 65 à 90. La position d'une minuscule dans l'alphabet, à partir de 0 pour a, est donc ord(c) - ord("a").
Exemple
Quelle est la valeur de chaque appel ?
def shift_letter(c, k):
    position = ord(c) - ord("a")
    new_position = (position + k) % 26
    return chr(ord("a") + new_position)

def caesar(s, k):
    result = ""
    for ch in s:
        result = result + shift_letter(ch, k)
    return result
>>> shift_letter("a", 3)
>>> shift_letter("z", 1)
>>> caesar("hello", 3)
>>> caesar("khoor", 23)

>>> shift_letter("a", 3)
'd'
>>> shift_letter("z", 1)
'a'
>>> caesar("hello", 3)
'khoor'
>>> caesar("khoor", 23)
'hello'
Le chiffre de César décale chaque lettre de k places dans l'alphabet. La lettre est transformée en sa position, décalée, et le reste de la division par 26 fait boucler l'alphabet : z décalé de un revient à a. Décaler de 23, c'est-à-dire 26 - 3, défait un décalage de 3 : la même fonction déchiffre. Cette version ne traite que les minuscules ; une espace ou une majuscule donnerait un caractère faux.

Une position de trop
Le dernier caractère est à la position len(s) - 1, pas len(s).
word = "Python"
print(word[6])
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print(word[6])
          ~~~~^^^
IndexError: string index out of range
Six caractères occupent les positions 0 à 5. La même faute se cache dans une boucle écrite for i in range(len(s) + 1) ; la faute voisine, range(1, len(s)), saute le premier caractère.
Signaler une erreur
Une chaîne ne se modifie pas
Affecter un caractère d'une chaîne est refusé : la chaîne est immuable.
word = "cat"
word[0] = "b"
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    word[0] = "b"
    ~~~~^^^
TypeError: 'str' object does not support item assignment
Pour obtenir bat, construire une nouvelle chaîne : word = "b" + word[1:]. Le nom word désigne maintenant une nouvelle chaîne et plus l'ancienne.
Signaler une erreur
Une méthode qui semble ne rien faire
Une méthode qui transforme une chaîne, comme upper, rend une nouvelle chaîne ; oublier de la ranger perd le résultat.
name = "ada"
name.upper()
print(name)
name = name.upper()
print(name)
ada
ADA
La deuxième ligne calcule "ADA" et la jette. La quatrième ligne la range sous le même nom, ce qui est la façon habituelle de l'écrire.
Signaler une erreur
Comparer des chaînes de chiffres
Les chaînes se comparent caractère par caractère, selon les codes des caractères, même quand elles ressemblent à des nombres.
print("10" < "9")
print(10 < 9)
True
False
Les premiers caractères sont comparés d'abord : "1" vient avant "9", donc "10" est avant "9". Une valeur tapée avec input() doit être convertie avec int() avant une comparaison numérique.
Signaler une erreur
Compétences à pratiquer
  • Prédire
  • Caractères et positions
  • Construire des chaînes
  • César
II Les listes
Un programme manipule souvent beaucoup de valeurs de même nature : les notes d'une classe, les articles d'une liste de courses, les lettres déjà essayées dans un jeu. Une variable par valeur est impossible quand leur nombre est inconnu. Une liste est une valeur qui en contient plusieurs, dans l'ordre, numérotées à partir de 0 comme les caractères d'une chaîne. À la différence d'une chaîne, une liste peut changer : des valeurs s'ajoutent, se remplacent, et c'est là qu'apparaît une nouvelle sorte d'erreur, deux noms pour la même liste.
Signaler une erreur
Définition — Les listes
Une liste s'écrit entre crochets, ses éléments séparés par des virgules : [3, 1, 2], ["ada", "leo"], et [] pour la liste vide. Les éléments sont numérotés à partir de 0 : lst[0] est le premier, lst[-1] le dernier quand la liste n'est pas vide, et len(lst) est le nombre d'éléments. Une liste peut contenir des valeurs de tout type, et une liste de chaînes est courante. Les tranches et * fonctionnent comme pour les chaînes : lst[1:3] est une liste de deux éléments, lst[:] la liste entière, et [0] * 3 vaut [0, 0, 0].
Définition — Modifier une liste
Une liste est mutable : l'affectation lst[i] = v remplace l'élément en position i, et la méthode lst.append(v) ajoute v à la fin. Les deux modifient la liste elle-même, sur place : aucune nouvelle liste n'est créée, et append rend None. Une liste construite à partir de [] grandit d'un élément à chaque append.
Définition — Parcourir et interroger une liste
La boucle for x in lst: s'exécute une fois par élément, x contenant l'élément courant ; la boucle for i in range(len(lst)): s'exécute une fois par position. Le test v in lst vaut True quand v est un élément de la liste, et v not in lst est le contraire. Sur une liste de nombres, les fonctions sum(lst), min(lst) et max(lst) rendent la somme, le plus petit et le plus grand élément, les deux dernières exigeant au moins un élément ; sorted(lst) rend une nouvelle liste, triée, sans modifier lst.
Exemple
Qu'affiche le programme suivant ?
items = []
items.append("bread")
items.append("milk")
items.append("eggs")
print(len(items))
print(items)
print(items[1])
for item in items:
    print("-", item)
print("milk" in items)

3
['bread', 'milk', 'eggs']
milk
- bread
- milk
- eggs
True
La liste commence vide et grandit d'un élément par append. Affichée entière, une liste montre ses crochets et ses chaînes avec des guillemets ; affichée élément par élément dans la boucle, les chaînes apparaissent sans guillemets, comme tout print d'une chaîne.

Méthode — Les quatre schémas de liste
  1. Construire : partir de [], parcourir quelque chose, append ce qui est produit.
  2. Compter : un compteur à 0, un parcours par élément, un if qui compte.
  3. Le plus grand à la main : sur une liste d'au moins un élément, partir du premier élément, parcourir les autres, remplacer quand plus grand ; parcourir par position quand la position est voulue aussi.
  4. Filtrer : partir de [], parcourir la liste, append les éléments qui passent le test. La liste de départ est intacte.
Exemple
Qu'affiche le programme suivant ?
grades = [12, 15, 9, 14]
total = 0
for g in grades:
    total = total + g
print(total / len(grades))
print(sum(grades) / len(grades))
print(max(grades), min(grades))
print(sorted(grades))
print(grades)

12.5
12.5
15 9
[9, 12, 14, 15]
[12, 15, 9, 14]
La moyenne à la main est le schéma de l'accumulateur sur une liste ; sum fait de même en un appel. Les deux dernières lignes montrent que sorted a rendu une nouvelle liste triée et a laissé grades dans son ordre de départ.

Exemple
Quelle est la valeur de chaque appel ?
def position_of_largest(lst):
    best = 0
    for i in range(1, len(lst)):
        if lst[i] > lst[best]:
            best = i
    return best

def evens(lst):
    result = []
    for x in lst:
        if x % 2 == 0:
            result.append(x)
    return result
>>> position_of_largest([4, 9, 2, 9])
>>> position_of_largest([7])
>>> evens([3, 8, 5, 2, 6])
>>> evens([1, 3])

>>> position_of_largest([4, 9, 2, 9])
1
>>> position_of_largest([7])
0
>>> evens([3, 8, 5, 2, 6])
[8, 2, 6]
>>> evens([1, 3])
[]
Le plus grand à la main, parcouru par position : best garde la position du plus grand élément vu jusque-là, et un élément suivant le remplace seulement s'il est strictement plus grand, donc le premier 9 gagne. Avec un seul élément la boucle fait zéro tour et 0 revient ; sans élément il n'y a pas de plus grand, et la fonction n'est pas faite pour être appelée. evens est le schéma du filtre : une nouvelle liste reçoit les éléments qui passent le test, et une liste vide revient si aucun ne passe.

Définition — Copies et alias
L'affectation b = a, où a est une liste, ne copie pas la liste : elle donne à la même liste un second nom. Les modifications faites par b se voient par a, et réciproquement ; b est un alias de a. Une vraie copie, une nouvelle liste aux mêmes éléments, s'obtient pour une liste de nombres ou de chaînes avec la tranche a[:], ou en la construisant avec une boucle et append. Il en va de même quand une liste est donnée à une fonction : le paramètre est un alias, et la fonction modifie la liste de l'appelant si elle applique append ou une affectation par index à son paramètre.
Exemple
Qu'affiche le programme suivant ?
a = [1, 2]
b = a
b.append(3)
print(a)
c = a[:]
c.append(4)
print(a)
print(c)

[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
La table des noms, une ligne par étape, les noms à gauche et la liste qu'ils nomment à droite :
après noms listes
b = a a, b une liste [1, 2]
b.append(3) a, b la même liste, maintenant [1, 2, 3]
c = a[:] a, b / c deux listes, toutes deux [1, 2, 3]
c.append(4) a, b / c [1, 2, 3] / [1, 2, 3, 4]
Ajouter par b a aussi modifié la liste que a nomme : une liste, deux noms. La tranche a fait une seconde liste, et lui ajouter un élément a laissé la première tranquille. La question à se poser avant d'écrire b = a sur une liste : copie, ou alias ?

append rend None
Ranger la valeur de append remplace la liste par None.
lst = [1, 2]
lst = lst.append(3)
lst.append(4)
Traceback (most recent call last):
  File "main.py", line 3, in <module>
    lst.append(4)
    ^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'append'
append modifie la liste et rend None, rien d'utilisable : il s'appelle sur sa propre ligne, lst.append(3), jamais à droite d'une affectation.
Signaler une erreur
sorted et sort
Deux façons de trier, aux habitudes opposées : sorted(lst) rend une nouvelle liste triée, lst.sort() trie la liste sur place et rend None.
lst = [3, 1, 2]
print(sorted(lst), lst)
lst.sort()
print(lst)
[1, 2, 3] [3, 1, 2]
[1, 2, 3]
Écrire lst = lst.sort() perd la liste, exactement comme append. sorted est le choix habituel : l'original reste disponible.
Signaler une erreur
Faire grandir la liste que l'on parcourt
Une boucle qui ajoute à la liste qu'elle parcourt n'en atteint jamais la fin : chaque tour ajoute un élément à parcourir.
lst = [1, 2, 3]
for x in lst:
    lst.append(x * 2)
Le programme ne s'arrête jamais ; l'éditeur en ligne l'interrompt après cinq secondes. Pour construire une liste à partir d'une autre, ajouter à une nouvelle liste, comme le fait le schéma du filtre.
Signaler une erreur
Encore une position de trop
Comme une chaîne, une liste de n éléments a les positions 0 à n - 1.
lst = [10, 20, 30]
print(lst[3])
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print(lst[3])
          ~~~^^^
IndexError: list index out of range
Le dernier élément est lst[len(lst) - 1] ou lst[-1] ; un parcours par position s'arrête à range(len(lst)).
Signaler une erreur
Compétences à pratiquer
  • Prédire
  • Construire et lire des listes
  • Les quatre schémas
  • Un carnet de notes
III Algorithmes sur les listes
Chaînes et listes en main, les mêmes quelques questions reviennent dans chaque programme : cette valeur est-elle dans la liste, et où ; une valeur apparaît-elle deux fois ; comment deux listes sont-elles liées ; comment un tableau de valeurs est-il rangé ; comment une phrase se découpe-t-elle en mots. Chacune a une réponse classique, un court programme qui vaut d'être su par cœur. Ce sont les algorithmes sur les listes ; l'algorithmique les étudie pour eux-mêmes.
Signaler une erreur
Définition — La recherche linéaire
Pour trouver une valeur dans une liste, la recherche linéaire parcourt les positions depuis la première et s'arrête au premier élément égal à la valeur : la fonction rend cette position aussitôt, et rend -1 après la boucle quand aucun élément n'a convenu. La valeur -1 signifie absent, puisque les positions rendues vont de 0 à len(lst) - 1. Le test v in lst ne répond que oui ou non ; lst.index(v) donne la position mais arrête le programme avec une erreur quand v est absent.
Méthode — Chercher puis décider
  1. Parcourir par position avec for i in range(len(lst)):.
  2. Tester l'élément courant ; quand c'est celui voulu, return i aussitôt : la recherche est finie.
  3. Après la boucle, écrire ce qui se passe quand rien n'a été trouvé : return -1. Cette ligne n'est atteinte que si chaque tour a échoué.
  4. Chez l'appelant, tester le résultat avec != -1 avant de s'en servir comme position.
Exemple
Quelle est la valeur de chaque appel ?
def find(lst, v):
    for i in range(len(lst)):
        if lst[i] == v:
            return i
    return -1

def has_duplicate(lst):
    for i in range(len(lst)):
        for j in range(i + 1, len(lst)):
            if lst[i] == lst[j]:
                return True
    return False
>>> find([5, 3, 8, 3], 3)
>>> find([5, 3, 8, 3], 7)
>>> find([], 1)
>>> has_duplicate([4, 1, 7, 1])
>>> has_duplicate([4, 1, 7])

>>> find([5, 3, 8, 3], 3)
1
>>> find([5, 3, 8, 3], 7)
-1
>>> find([], 1)
-1
>>> has_duplicate([4, 1, 7, 1])
True
>>> has_duplicate([4, 1, 7])
False
find rend la première position de 3, et -1 quand la valeur est absente ou la liste vide, puisque la boucle fait alors zéro tour. has_duplicate compare chaque élément avec ceux qui le suivent : deux boucles, l'intérieure commençant à i + 1 pour que chaque paire soit comparée une fois et qu'un élément ne soit jamais comparé à lui-même. Les deux fonctions rendent leur valeur depuis la boucle dès que la réponse est connue.

Définition — Les listes parallèles
Deux listes de même longueur sont parallèles quand les éléments de même position vont ensemble : names[i] et scores[i] décrivent le même élève. Un parcours par position lit les deux à la fois ; la position trouvée dans une liste sert dans l'autre. Un même individu, un élève, est ainsi décrit par plusieurs listes, une par propriété.
Exemple
Qu'affiche le programme suivant ?
names = ["Ada", "Leo", "Zoe"]
scores = [15, 12, 17]
best = 0
for i in range(1, len(names)):
    if scores[i] > scores[best]:
        best = i
print(names[best], scores[best])
for i in range(len(names)):
    if scores[i] >= 15:
        print(names[i])

Zoe 17
Ada
Zoe
Deux listes parallèles, parcourues par position : le schéma du plus grand à la main tourne sur scores et donne une position, 2, qui sert ensuite dans names. La seconde boucle teste une liste et affiche depuis l'autre. Rien ne lie les deux listes sauf les positions, et c'est pourquoi elles doivent garder la même longueur et le même ordre.

Exemple
Qu'affiche le programme suivant ?
text = "les lettres les plus frequentes"
counts = [0] * 26
total = 0
for c in text:
    if c in "abcdefghijklmnopqrstuvwxyz":
        position = ord(c) - ord("a")
        counts[position] = counts[position] + 1
        total = total + 1
best = 0
for i in range(26):
    if counts[i] > counts[best]:
        best = i
print(chr(best + ord("a")), counts[best], total)

e 7 27
La liste counts contient un compteur par lettre : la position 0 pour a, la position 25 pour z, et, les minuscules ayant des codes consécutifs, ord(c) - ord("a") change une lettre en sa position. Un seul parcours du texte suffit à remplir les \(26\) compteurs ; les espaces échouent au test et ne sont pas comptées, donc total est le nombre de lettres, 27. La seconde boucle est le schéma du plus grand à la main sur les \(26\) positions, et chr change la position gagnante en lettre : e, vue 7 fois, soit une fréquence de \(\frac{7}{27}\), environ \(0{,}26\). Les \(26\) fréquences, positives ou nulles et de somme \(1\), forment une loi de probabilité : celle d'une lettre tirée au hasard parmi les lettres du texte. Sur un long texte, les fréquences des \(26\) lettres deviennent caractéristiques de sa langue — en français comme en anglais, e est la lettre la plus fréquente. C'est ainsi qu'on casse un chiffre de César : la lettre la plus fréquente du texte chiffré est très probablement un e décalé.

Définition — Les listes de listes
Un élément d'une liste peut être une liste : grid = [[1, 2, 3], [4, 5, 6]] est un tableau de deux lignes et trois colonnes. grid[r] est la ligne numéro r, une liste, et grid[r][c] l'élément de la ligne r et de la colonne c ; pour une grille dont les lignes ont toutes la même longueur, len(grid) est le nombre de lignes et len(grid[0]) le nombre de colonnes. Un tableau se parcourt avec deux boucles, une sur les lignes et une sur les colonnes.
Exemple
Qu'affiche le programme suivant ?
grid = [["X", "O", "X"], [" ", "X", "O"], ["O", " ", "X"]]
for row in grid:
    print(row[0] + "|" + row[1] + "|" + row[2])
count = 0
for r in range(3):
    for c in range(3):
        if grid[r][c] == "X":
            count = count + 1
print("X:", count)
print(grid[1][2])

X|O|X
 |X|O
O| |X
X: 4
O
La première boucle parcourt les lignes, chaque ligne étant une liste de trois chaînes collées avec des barres. Le compteur doit visiter chaque case : deux boucles imbriquées sur range(3), les lignes puis les colonnes. grid[1][2] est le troisième élément de la deuxième ligne : les lignes d'abord, puis les colonnes, les deux à partir de 0.

Définition — Découper et recoller
La méthode s.split() coupe la chaîne s à ses espaces, plusieurs espaces de suite comptant pour une, et rend la liste de ses mots ; s.split(",") coupe à chaque virgule à la place. Dans l'autre sens, sep.join(lst) colle les chaînes de la liste lst avec le séparateur sep entre elles, par exemple " ".join(words) reconstruit une phrase.
Exemple
Quelle est la valeur de chaque appel ?
def longest_word(sentence):
    words = sentence.split()
    best = words[0]
    for w in words:
        if len(w) > len(best):
            best = w
    return best

def word_count(sentence):
    return len(sentence.split())
>>> "a red fox".split()
>>> longest_word("the quick brown fox")
>>> word_count("the quick brown fox")
>>> "-".join(["a", "b", "c"])

>>> "a red fox".split()
['a', 'red', 'fox']
>>> longest_word("the quick brown fox")
'quick'
>>> word_count("the quick brown fox")
4
>>> "-".join(["a", "b", "c"])
'a-b-c'
split transforme une phrase en liste de mots, et le schéma du plus grand à la main tourne alors sur les longueurs : quick et brown ont cinq lettres tous deux, et la comparaison stricte garde le premier. Compter les mots, c'est compter les éléments de cette liste. longest_word part de words[0] : la phrase doit contenir au moins un mot.

Définition — Liste en compréhension
Une liste peut se construire en une seule expression à partir d'un parcours : [expr for x in lst] est la liste des valeurs de l'expression expr, calculée pour chaque élément x de lst, dans l'ordre. On l'appelle une liste en compréhension. Le parcours peut être un range : [n * n for n in range(5)] vaut [0, 1, 4, 9, 16], les cinq premiers termes de la suite des carrés. Un if écrit à la fin ne garde que les éléments qui passent un test : [x for x in lst if x > 0] est la liste des éléments strictement positifs de lst, dans leur ordre. Une liste en compréhension est le schéma de construction, ou le schéma du filtre, écrit en une ligne : elle rend une nouvelle liste, celle que construirait une boucle partant de [] avec un append à chaque tour, et lst reste intacte.
Exemple
Qu'affiche le programme suivant ?
u = [3 * n + 2 for n in range(5)]
print(u)

def f(x):
    return x * x - 4

xs = [-3, -1, 0, 2, 5]
ys = [f(x) for x in xs]
print(ys)
print([y for y in ys if y > 0])
print([n for n in range(1, 31) if n % 7 == 0])

[2, 5, 8, 11, 14]
[5, -3, -4, 0, 21]
[5, 21]
[7, 14, 21, 28]
La première liste en compréhension parcourt range(5), donc n prend les valeurs de 0 à 4 : elle donne les cinq premiers termes de la suite définie par \(u_n = 3n + 2\), une suite arithmétique de premier terme \(2\) et de raison \(3\). La deuxième parcourt la liste xs et donne les images par \(f\) de ses éléments, dans le même ordre : un tableau de valeurs de \(f\), rangé dans une liste. Le if de la troisième garde les valeurs qui passent le test : 0 n'est pas strictement supérieur à 0, donc l'image de 2 est écartée. La quatrième filtre un range : range(1, 31) va de 1 à 30, et seuls les multiples de 7 sont gardés. La liste ys pourrait aussi se construire avec le schéma de construction, en trois lignes :
ys = []
for x in xs:
    ys.append(f(x))
La liste en compréhension dit la même chose en une ligne, dans l'ordre d'une phrase : la valeur, puis le parcours, puis le test.

-1 n'est pas False
Tester le résultat d'une recherche avec un if sans comparaison est faux : parmi les entiers, seul 0 compte comme faux, et 0 est une position valable.
def find(lst, v):
    for i in range(len(lst)):
        if lst[i] == v:
            return i
    return -1

position = find([7, 3], 7)
if position:
    print("found at", position)
else:
    print("not found")
not found
La valeur a été trouvée en position 0, que le if considère comme faux, alors que -1 serait considéré comme vrai. Écrire la comparaison en entier : if position != -1:.
Signaler une erreur
Des listes parallèles qui se décalent
Des listes parallèles doivent grandir ensemble : un append sur l'une sans l'autre casse l'appariement, et le parcours échoue sur la liste la plus courte.
names = ["Ada", "Leo", "Zoe"]
scores = [15, 12]
for i in range(len(names)):
    print(names[i], scores[i])
Ada 15
Leo 12
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    print(names[i], scores[i])
                    ~~~~~~^^^
IndexError: list index out of range
Chaque fois qu'un élève est ajouté, les deux listes reçoivent un élément, dans le même ordre.
Signaler une erreur
Une ligne partagée
Construire une grille en répétant une ligne fait de chaque ligne un alias de la même liste.
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
Une seule case a été modifiée, et trois lignes le montrent : les trois lignes sont une seule liste à trois noms. Construire les lignes une par une avec une boucle et append, pour que chaque ligne soit une nouvelle liste.
Signaler une erreur
Compétences à pratiquer
  • Prédire
  • Chercher
  • Les grilles
  • Construire des listes en compréhension
IV Projet : le pendu
La machine choisit un mot secret ; le joueur propose des lettres une à une ; après chaque lettre le mot est montré avec ses lettres trouvées et des tirets pour les autres, et une lettre fausse coûte une erreur. Six erreurs font perdre la partie, le mot entier trouvé la fait gagner. Chaînes, listes, boucles et fonctions y participent toutes : le mot est une chaîne parcourue par caractère, les lettres proposées sont une liste qui grandit, l'affichage est une chaîne construite lettre par lettre.
Signaler une erreur
Méthode — Un jeu avec une liste d'état
  1. Écrire l'état : le mot secret, la liste des lettres proposées jusque-là, le nombre d'erreurs.
  2. Écrire l'affichage comme une fonction de l'état : masked(word, letters) construit la chaîne montrée au joueur, et se teste par des appels avant tout le reste.
  3. Écrire le test de fin comme une fonction : is_won(word, letters). La boucle continue tant que la partie n'est ni gagnée ni perdue.
  4. Écrire un tour : montrer, lire une lettre, l'ajouter à la liste, compter l'erreur si la lettre n'est pas dans le mot.
  5. Écrire la fin à partir de l'état, puis ajouter les extras.
Exemple
Le tirage du mot est 1 et le joueur tape o, l, x et p. Qu'affiche le programme suivant ?
import random

def masked(word, letters):
    result = ""
    for ch in word:
        if ch in letters:
            result = result + ch
        else:
            result = result + "-"
    return result

def is_won(word, letters):
    return masked(word, letters) == word

words = ["python", "loop", "list"]
secret = words[random.randint(0, len(words) - 1)]
letters = []
errors = 0
while errors < 6 and not is_won(secret, letters):
    print(masked(secret, letters))
    guess = input("Letter? ")
    letters.append(guess)
    if guess not in secret:
        errors = errors + 1
        print("Wrong! Errors:", errors)
if is_won(secret, letters):
    print("You won:", secret)
else:
    print("You lost! The word was", secret)

----
Letter? o
-oo-
Letter? l
loo-
Letter? x
Wrong! Errors: 1
loo-
Letter? p
You won: loop
Le tirage 1 choisit loop, le deuxième mot de la liste, puisque les positions commencent à 0. masked est le schéma de construction d'une chaîne avec un test sur ch in letters : la liste des lettres proposées est parcourue pour chaque caractère du mot. is_won réutilise masked : le mot est trouvé quand plus rien n'est masqué. La boucle s'arrête dès que l'une de ses deux conditions tombe ; la fin demande laquelle. Le programme suppose une minuscule par tour ; les variantes des exercices vérifient la lettre tapée. Chaque pièce est déjà connue : l'état et le tour du jeu de devinette, les fonctions, la chaîne et la liste.

Compétences à pratiquer
  • Construire le jeu du pendu et ses variantes
V Pour aller plus loin
Compétences à pratiquer
  • Aller plus loin avec les listes
  • Quiz