Leçon 03-2 — Slicing et parcours de listes
1. Objectif
À la fin de cette leçon tu sauras : découper n'importe quelle tranche d'une liste ou d'une chaîne avec [start:stop:step], copier une liste avec [:], parcourir proprement avec for et enumerate, construire une liste en boucle, et éviter le piège du parcours d'une liste qu'on modifie en même temps.
2. Pourquoi c'est important
« Les 3 premiers », « les 5 derniers », « un élément sur deux », « tout sauf le dernier » : le découpage de données est un besoin quotidien — pagination, podiums, extraits, historiques. Le slicing le fait en UNE expression lisible, là où d'autres langages écrivent des boucles. Et le duo parcours + construction de liste est LE motif de transformation de données que tu écriras des milliers de fois (il deviendra encore plus puissant avec les fonctions au niveau 4 et les fichiers au niveau 5).
3. Explication simple
Une slice (tranche), c'est « donne-moi les casiers de start inclus à stop exclu » :
nums = [10, 20, 30, 40, 50]
print(nums[1:4]) # [20, 30, 40] : casiers 1, 2, 3 — le 4 est EXCLU
Imagine les index comme des coups de couteau entre les casiers, pas sur les casiers :
0 1 2 3 4
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
0 1 2 3 4 5
↑couteau ↑couteau nums[1:4] → ce qu'il y a entre les deux
Si tu omets un bord, Python prend « depuis le début » ou « jusqu'à la fin » : nums[:3] (les 3 premiers), nums[2:] (de l'index 2 à la fin), nums[:] (tout — une copie). Et le parcours, tu le connais déjà (⏪ niveau 02) : for n in nums: visite chaque casier dans l'ordre.
4. Explication approfondie
a) Pourquoi stop est exclu. Même convention que range (⏪ niveau 02), et elle rend les calculs propres : nums[:k] contient exactement k éléments ; nums[:k] + nums[k:] reconstitue exactement la liste, sans trou ni doublon. Une fois qu'on l'accepte, cette convention élimine des off-by-one au lieu d'en créer.
b) Le troisième nombre : le pas. nums[::2] = un élément sur deux ; nums[::-1] = la liste à l'envers (pas de −1). Les slices marchent sur les chaînes aussi : "python"[1:4] → "yth", "python"[::-1] → "nohtyp".
c) Une slice ne plante JAMAIS. nums[1:100] sur 5 éléments ne lève pas d'IndexError : Python rabote aux bornes réelles et renvoie ce qui existe ([20, 30, 40, 50]), quitte à renvoyer []. Confort… et piège : une borne fausse ne fait pas de bruit (section 6).
d) Une slice fabrique une NOUVELLE liste. nums[1:4] ne modifie pas nums — c'est une opération « qui renvoie », jamais « sur place » (⏪ la distinction de 03-1). Cas particulier précieux : nums[:] copie toute la liste. ⏩ POURQUOI on a besoin de copier — et pourquoi b = nums ne copie rien — est le sujet de la leçon 03-4, la plus importante du niveau.
e) enumerate : l'index ET la valeur. Quand tu as besoin des deux, n'écris pas for i in range(len(nums)) (lourd, et source d'off-by-one) :
for i, name in enumerate(names): # i, name : déballage — deux
print(i, name) # variables remplies d'un coup
enumerate(names, start=1) fait démarrer le compte à 1 — parfait pour l'affichage humain. ⏩ Le déballage vient des tuples, détaillé en 03-3.
f) Le piège du niveau : modifier la liste qu'on parcourt. Retirer des éléments PENDANT un for sur cette même liste fait sauter des éléments, sans aucune erreur (démonstration à l'exemple 6). Règle : on parcourt une copie (for n in nums[:]), ou mieux, on construit une nouvelle liste avec ce qu'on garde. Ce piège produit des bugs sournois même chez des développeurs expérimentés — l'exemple 6 est à taper et à retenir.
5. Exemples commentés
Exemple 1 — slices de base
nums = [10, 20, 30, 40, 50]
print(nums[1:4]) # [20, 30, 40] : de 1 inclus à 4 exclu
print(nums[:3]) # [10, 20, 30] : les 3 premiers
print(nums[2:]) # [30, 40, 50] : de l'index 2 à la fin
print(nums[-3:]) # [30, 40, 50] : les 3 DERNIERS
print(nums[:-1]) # [10, 20, 30, 40] : tout sauf le dernier
Exemple 2 — pas, inversion, chaînes
nums = [10, 20, 30, 40, 50]
print(nums[::2]) # [10, 30, 50] : un sur deux
print(nums[::-1]) # [50, 40, 30, 20, 10] : à l'envers
word = "python"
print(word[1:4]) # yth : les chaînes se découpent pareil
print(word[::-1]) # nohtyp : très utile pour les palindromes
Exemple 3 — la slice renvoie, elle ne modifie pas ; [:] copie
nums = [10, 20, 30]
top = nums[:2]
print(top) # [10, 20]
print(nums) # [10, 20, 30] : intact — une slice ne modifie JAMAIS
copy = nums[:] # copie complète
copy.append(99)
print(nums) # [10, 20, 30] : l'originale n'a pas bougé
print(copy) # [10, 20, 30, 99]
⏩ Pourquoi copy = nums (sans [:]) n'aurait PAS protégé l'originale : leçon 03-4.
Exemple 4 — enumerate pour numéroter
podium = ["Ada", "Linus", "Guido"]
for i, name in enumerate(podium, start=1):
print(f"{i}. {name}")
Sortie :
1. Ada
2. Linus
3. Guido
enumerate fournit à chaque tour une paire (index, valeur) que le for déballe dans i et name. Sans start=1, i commence à 0.
Exemple 5 — construire une liste en boucle (LE motif à automatiser)
squares = [] # 1. on part d'une liste vide
for n in range(1, 6): # 2. on parcourt une source
squares.append(n * n) # 3. on accumule les résultats
print(squares) # [1, 4, 9, 16, 25]
C'est l'accumulateur du niveau 02, version liste. Transformation, filtrage, collecte : tout passe par ce motif à trois temps.
Exemple 6 — ⚠️ le piège : retirer pendant qu'on parcourt
nums = [2, 4, 6, 8]
for n in nums:
if n % 2 == 0:
nums.remove(n) # on retire DANS la liste qu'on parcourt
print(nums) # [4, 8] ← et non [] !
Tous les éléments sont pairs, on s'attend à tout retirer… il en reste deux. Pourquoi : quand remove(2) retire le casier 0, tout se décale à gauche ; le curseur du for, lui, avance quand même → le 4 (devenu casier 0) est sauté. Aucune erreur, résultat faux. Les deux corrections :
nums = [2, 4, 6, 8]
for n in nums[:]: # correction 1 : on parcourt une COPIE figée,
if n % 2 == 0: # on modifie l'originale
nums.remove(n)
print(nums) # []
nums = [2, 4, 6, 8]
odds = [] # correction 2 (préférée) : construire une
for n in nums: # NOUVELLE liste avec ce qu'on garde
if n % 2 != 0:
odds.append(n)
print(odds) # []
6. Erreurs fréquentes
1) Croire que stop est inclus
nums = [10, 20, 30, 40, 50]
print(nums[0:2]) # [10, 20] — PAS [10, 20, 30]
« Les 3 premiers » = nums[:3], pas nums[:2]. Astuce anti-doute : stop - start = nombre d'éléments obtenus.
2) Une borne trop grande ne prévient pas
print(nums[10:20]) # [] — pas d'IndexError, juste une liste vide
Contrairement à nums[10] qui planterait, la slice se tait. Si ton programme « perd » mystérieusement des données, affiche la slice ET ses bornes.
3) start après stop : liste vide silencieuse
print(nums[4:1]) # [] — le couteau de gauche est APRÈS celui de droite
(Avec un pas positif, Python avance de start vers stop ; il n'y a rien à prendre.) Souvent le symptôme de bornes calculées dans le mauvais ordre.
4) Oublier de déballer enumerate
for item in enumerate(["a", "b"]):
print(item) # (0, 'a') puis (1, 'b') — des paires entières !
Sans les deux variables (for i, x in ...), tu reçois le tuple complet. Pas d'erreur, mais un affichage (0, 'a') là où tu attendais a.
5) Modifier la liste qu'on parcourt — l'exemple 6. Symptôme : des éléments « survivent » au filtrage, un sur deux en général. Réflexe : dès que tu écris remove/pop/append sur la liste du for, arrête-toi et passe par une copie ou une nouvelle liste.
6) « Copier » avec =
copy = nums # ce n'est PAS une copie (aucune erreur, hélas)
copy.append(99) # ... nums vient aussi de "recevoir" 99 !
copy = nums ne crée aucune nouvelle liste — les deux noms désignent la même. La vraie copie s'écrit nums[:] ou nums.copy(). ⏩ L'explication complète (et les schémas) : leçon 03-4. Si tu ne dois retenir qu'un avant-goût : = ne copie jamais une liste.
7. Lire les messages d'erreur
Bonne nouvelle : les slices ne lèvent pratiquement jamais d'exception — leurs bugs sont silencieux (sections 6.2 et 6.3), et se déboguent au print. Les tracebacks de cette leçon viennent donc du parcours.
Se tromper de variable de déballage :
Traceback (most recent call last):
File "C:\Users\jtron\Claude\learnpython\ranking.py", line 2, in <module>
for i, n in nums:
^^^^
TypeError: cannot unpack non-iterable int object
De bas en haut : cannot unpack non-iterable int object = « tu essaies de déballer un int en deux variables ». Le for i, n in nums: promet des paires, mais nums contient des entiers simples — il manque enumerate : for i, n in enumerate(nums):. Les ^^^^ pointent la cible du déballage.
Indexer avec autre chose qu'un entier :
Traceback (most recent call last):
File "C:\Users\jtron\Claude\learnpython\ranking.py", line 3, in <module>
print(nums[choice])
~~~~^^^^^^^^
TypeError: list indices must be integers or slices, not str
list indices must be integers or slices, not str = l'index fourni est une chaîne. Cause n° 1 : un input() jamais converti (⏪ niveau 01) — choice = input(...) renvoie "2", pas 2. Correction : choice = int(input(...)).
Et si tu recroises IndexError : c'est un accès simple nums[i] (pas une slice) — relis la section 7 de la leçon 03-1.
8. Exercices — faciles
Règles : un fichier par exercice dans
lessons/level-03-structures-donnees/exercices/mes-reponses/, nomméex-03-2-a.py, etc. Prédis chaque sortie AVANT d'exécuter.
a) Avec ranking = ["Ada", "Linus", "Guido", "Grace", "Tim"], affiche en quatre print et uniquement des slices : le podium (3 premiers), les 2 derniers, tout sauf le premier, le classement inversé.
b) Prédis sur papier les cinq sorties, puis vérifie :
nums = [10, 20, 30, 40, 50]
print(nums[1:3])
print(nums[:2])
print(nums[3:])
print(nums[1:100])
print(nums[::2])
c) Demande un mot avec input() et affiche-le à l'envers. Puis affiche Palindrome ! ou Pas un palindrome. selon le cas (compare le mot et son inverse ; teste avec kayak et python).
9. Exercices — moyens
a) Ce programme doit garder uniquement les mots de 3 lettres ou moins, mais son résultat est faux. Prédis ce qu'il affiche réellement, explique pourquoi le mot fautif survit, puis écris les DEUX corrections (copie, puis nouvelle liste) :
words = ["le", "python", "genial", "est", "ok"]
for word in words:
if len(word) > 3:
words.remove(word)
print(words)
b) Avec temps = [12, 19, 12, 7, 12] : construis (en boucle, avec enumerate) la liste des index où apparaît la valeur 12. Sortie attendue : [0, 2, 4]. Puis affiche chaque position sur sa ligne au format Jour 1 : 12°C (attention : jour = index + 1).
c) Reprends la liste de courses du mini-projet 03-1 : ajoute au listing un numéro devant chaque article (1. cafe) avec enumerate(..., start=1) — et explique en commentaire pourquoi start=1 évite un + 1 dans la f-string.
10. Exercices — difficiles
a) Rotation de liste. Écris un programme qui fait « tourner » une liste de k positions vers la gauche : [1, 2, 3, 4, 5] avec k = 2 devient [3, 4, 5, 1, 2]. En UNE ligne de slices (pas de boucle). Puis la rotation vers la droite. Vérifie avec k = 0 et k = 5 et explique ce que tu observes.
- Indice 1 : découpe mentalement la liste en deux blocs : celui qui part à la fin, celui qui passe devant.
- Indice 2 :
nums[k:]etnums[:k]sont exactement ces deux blocs. - Indice 3 : recolle-les dans le bon ordre avec
+; pour la droite, pense aux index négatifs (nums[-k:]…).
b) Un sur deux, sans le pas. Reconstruis nums[::2] sans utiliser de slice à pas : en boucle avec enumerate, garde les éléments d'index pair. Compare les deux versions dans un commentaire : laquelle préfères-tu, et pourquoi ?
11. Mini-projet lié — « pagination »
Écris paginate.py : le programme contient une liste d'au moins 10 titres (films, morceaux, livres…) et l'affiche page par page, 3 titres à la fois :
--- Page 1 ---
1. Dune
2. Alien
3. Brazil
[Entree pour la suite, q pour quitter]
Contraintes : la taille de page est une variable PAGE_SIZE = 3 (le programme doit marcher en la passant à 4) ; chaque page est extraite avec UNE slice (items[start:start + PAGE_SIZE]) ; la numérotation est continue d'une page à l'autre (la page 2 commence à 4 — enumerate a un start pour ça) ; la dernière page peut être incomplète et ne doit pas planter (relis la section 4.c : les slices rabotent). Durée cible : 30–45 min.
12. Correction / méthode de correction
Les solutions détaillées sont dans solutions/serie-03-2-solutions.md.
Méthode : (1) tous les exercices d'abord ; (2) compare le raisonnement, pas seulement le code ; (3) note chaque écart dans le journal ; (4) pour 9-a, si ta prédiction était fausse, déroule l'exécution en dessinant la liste ET la position du curseur à chaque tour — c'est le seul moyen de VOIR le saut.
13. À retenir
lst[start:stop]:startinclus,stopexclu — commerange.- Bords omis = « depuis le début » / « jusqu'à la fin » ;
lst[:]= copie. lst[::-1]inverse ;lst[::2]= un sur deux ; marche aussi sur les chaînes.- Une slice renvoie une nouvelle liste, ne modifie jamais, ne plante jamais (elle rabote aux bornes réelles).
enumerate(lst, start=1)donne index + valeur ; toujours préférable àrange(len(lst)).- Construire une liste = liste vide + boucle +
append. - Ne jamais retirer des éléments de la liste qu'on est en train de parcourir : copie
[:]ou nouvelle liste. =ne copie pas une liste (⏩ 03-4).
14. Questions de révision
- Combien d'éléments contient
lst[2:7](si la liste est assez longue) ? Quelle formule le donne ? - Pourquoi
lst[:k] + lst[k:]reconstitue-t-il toujours la liste exacte ? - Que renvoie
nums[10:20]sur une liste de 5 éléments, et pourquoi est-ce à la fois pratique et dangereux ? - Quelle est la différence entre
for x in lstetfor i, x in enumerate(lst)? - Décris le motif en trois temps pour construire une liste en boucle.
- Pourquoi retirer des éléments pendant un
forsur la même liste saute-t-il des éléments ? Quelles sont les deux parades ? - Qu'affiche
print(word[::-1])pourword = "kayak", et à quoi ce genre de slice sert-il ? - Que reproche
TypeError: list indices must be integers or slices, not str, et quelle en est la cause la plus fréquente ?
15. Checklist de compréhension
- Je peux extraire « les n premiers », « les n derniers » et « tout sauf le dernier » sans réfléchir plus de cinq secondes.
- Je sais expliquer avec le dessin du couteau pourquoi
stopest exclu. - Je sais copier une liste avec
[:]et je sais que=ne copie pas. - Je peux réécrire une boucle
range(len(lst))avecenumerate. - Je peux expliquer À VOIX HAUTE pourquoi
[2, 4, 6, 8]filtré pendant son parcours donne[4, 8]. - J'ai fait le mini-projet et la dernière page incomplète s'affiche sans erreur.
Si une case reste vide : refais les exercices de la section correspondante demain, AVANT d'ouvrir la leçon suivante.
16. Commit conseillé
git add lessons/level-03-structures-donnees/exercices/mes-reponses/
git commit -m "exercises: complete lesson 03-2 slicing and iteration"
Puis, si c'est ta fin de session : entrée de journal + git push.