Leçon 02-4 — for et range : répéter pour chaque

1. Objectif

À la fin de cette leçon tu sauras : écrire une boucle for ; utiliser range sous ses trois formes (range(stop), range(start, stop), range(start, stop, step)) et dire EXACTEMENT quelles valeurs chacune produit ; parcourir les caractères d'une chaîne ; sortir d'une boucle avec break et sauter un tour avec continue ; et imbriquer deux boucles sans te perdre.

2. Pourquoi c'est important

Le while de la leçon 02-3 répète « tant qu'on ne sait pas quand ça s'arrête ». Mais la plupart des répétitions du vrai code sont l'inverse : « pour CHAQUE élément de ceci » — chaque nombre de 1 à 100, chaque caractère d'un texte, chaque ligne d'un fichier (⏩ niveau 5), chaque élément d'une liste (⏩ niveau 3, où le for prendra toute sa dimension). Le for est la boucle que tu écriras le plus souvent de ta vie. Et range est son compagnon — avec son piège fondateur : range(10) s'arrête à 9. La moitié des bugs « off-by-one » du monde viennent de là.

3. Explication simple

for se lit « POUR CHAQUE valeur de la séquence, fais le bloc » :

for i in range(5):
    print(i)

Sortie :

0
1
2
3
4

range(5) produit les entiers de 0 à 4 — cinq valeurs, en partant de 0, et 5 n'en fait PAS partie. À chaque tour, la variable i reçoit la valeur suivante, puis le bloc s'exécute. Pas d'initialisation, pas de mise à jour, pas de condition d'arrêt à écrire : le for gère tout — c'est sa grande force sur le while quand le nombre de tours est connu.

Les trois formes de range :

ÉcritureProduitÀ retenir
range(5)0 1 2 3 4démarre à 0, s'arrête AVANT stop
range(1, 6)1 2 3 4 5start inclus, stop exclu
range(0, 20, 5)0 5 10 15avance de step en step

La règle unique : start inclus, stop exclu. Pour obtenir « de 1 à 10 », il faut écrire range(1, 11).

4. Explication approfondie

a) Pourquoi stop est exclu ? Convention (héritée du monde entier de l'informatique, on la retrouvera dans le slicing des chaînes et des listes ⏩ niveaux 1-3 et 3). Elle a deux vertus : range(n) fait exactement n tours, et range(a, b) en fait b - a. Vérification systématique quand tu doutes : écris à la main la première et la dernière valeur produites. range(1, 11) → première 1, dernière 10. Trente secondes qui évitent l'erreur la plus fréquente de la leçon.

b) Le pas, y compris négatif. range(10, 0, -2) produit 10 8 6 4 2 — départ 10, on descend de 2, on s'arrête AVANT 0. Un range qui ne peut pas avancer est simplement vide : range(5, 5) ou range(1, 10, -1) ne produisent rien — zéro tour, zéro erreur (le cousin du while faux d'entrée, leçon 02-3).

c) for parcourt bien plus que range. Une chaîne de caractères est une séquence : for letter in "python": donne à letter chaque caractère, dans l'ordre. Au passage, tu peux tester l'appartenance avec l'opérateur in (le même mot que dans le for, utilisé cette fois comme comparaison) : "a" in "chat" vaut True. ⏩ Au niveau 3, listes, tuples et dictionnaires se parcourront exactement pareil — le for que tu apprends ici est déjà le bon.

d) break et continue. Deux mots-clés qui court-circuitent le déroulement, dans un for comme dans un while :

À utiliser avec sobriété : une boucle truffée de break/continue devient illisible. Souvent, un if bien placé ou une meilleure condition fait plus propre. La règle de bon sens : un break par boucle, motivé, ou rien. Piège spécifique au while : un continue placé AVANT la ligne de mise à jour saute la mise à jour → boucle infinie. Dans un for, aucun risque : le passage à la valeur suivante est automatique.

e) Boucles imbriquées. Un for dans un for : la boucle intérieure s'exécute EN ENTIER pour chaque tour de l'extérieure (3 × 3 = 9 passages pour deux boucles de 3). Deux règles d'hygiène : des noms de variables distincts et parlants (row/col, pas i/j dès que possible), et savoir que break ne sort que de la boucle la plus proche — la boucle extérieure continue.

f) for ou while ? Le choix en une question : « est-ce que je sais, au moment d'écrire la boucle, sur QUOI je répète ? » Oui (n tours, chaque caractère…) → for. Non (jusqu'à ce que l'utilisateur réussisse, jusqu'à convergence…) → while. Si tu écris un while avec un compteur qui va de 0 à n, c'est un for déguisé — réécris-le.

Difficulté honnête : le for simple s'acquiert en une session. Les deux points qui demandent de la répétition : les bornes de range (fais le test première/dernière valeur jusqu'à ce qu'il soit automatique) et les boucles imbriquées (déroule-les sur papier au moins une fois). Prévois 2–3 sessions.

5. Exemples commentés

Exemple 1 — les trois formes de range

for i in range(1, 6):
    print(i, end=" ")      # end=" " : espace au lieu du retour à la ligne
print()
for i in range(0, 20, 5):
    print(i, end=" ")
print()
for i in range(10, 0, -2):
    print(i, end=" ")
print()

Sortie :

1 2 3 4 5
0 5 10 15
10 8 6 4 2

(Le end=" " est une option de print() bien pratique pour compacter les sorties de boucles — le print() seul ferme la ligne.)

Exemple 2 — accumulateur avec for : la somme de 1 à 10

total = 0
for number in range(1, 11):    # 11, pas 10 : stop exclu !
    total += number
print(total)

Sortie : 55. Compare avec l'exemple 2 de la leçon 02-3 : même motif accumulateur, mais plus de compteur à gérer à la main — le for s'en charge.

Exemple 3 — parcourir une chaîne : compter les voyelles

vowel_count = 0
for letter in "programmation":
    if letter in "aeiou":      # in : appartenance — letter est-elle dedans ?
        vowel_count += 1
print(vowel_count)

Sortie : 5 (o, a, a, i, o). Un for + un if + un compteur : ce trio résout une famille entière de problèmes (« compte les X qui vérifient Y »).

Exemple 4 — break : arrêter dès qu'on a trouvé

for n in range(2, 20):
    if n * n > 50:
        print(f"premier carre au-dessus de 50 : {n} * {n} = {n * n}")
        break                  # trouvé : inutile de tester 9, 10, 11...

Sortie : premier carre au-dessus de 50 : 8 * 8 = 64. Sans le break, le programme afficherait AUSSI 9, 10, … 19 — « le premier » exige de s'arrêter.

Exemple 5 — continue : sauter les cas sans intérêt

for n in range(1, 8):
    if n % 2 == 0:
        continue               # pair : on abandonne CE tour, suivant !
    print(n, end=" ")
print()

Sortie : 1 3 5 7. (Honnêtement : if n % 2 == 1: print(...) — sans continue — ferait aussi bien ici. continue gagne quand le bloc restant est long et qu'on veut éviter d'indenter dix lignes dans un if.)

Exemple 6 — boucles imbriquées : la table de multiplication

for row in range(1, 4):
    for col in range(1, 4):
        print(f"{row} x {col} = {row * col}")
    print("---fin ligne---")   # indenté UNE fois : dans row, hors de col

Sortie :

1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
---fin ligne---
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
---fin ligne---
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
---fin ligne---

Pour CHAQUE row, la boucle col déroule ses trois tours. Note les trois niveaux d'indentation : le print du séparateur est dans row mais hors de col — déplace-le d'un niveau et la sortie change du tout au tout (⏩ 02-5).

6. Erreurs fréquentes

1) L'off-by-one de range — LE classique

total = 0
for n in range(1, 10):     # voulait la somme de 1 à 10...
    total += n
print(total)               # 45 — il manque le 10 (attendu : 55)

Aucune erreur : juste un résultat faux de 10. stop est exclu : « de 1 à 10 » = range(1, 11). Antidote systématique : première valeur, dernière valeur, écrites à la main.

2) Donner un simple nombre à for

count = int(input("Combien ? "))
for i in count:
    print(i)
# TypeError: 'int' object is not iterable

for a besoin d'une séquence (quelque chose à parcourir) ; un entier nu n'en est pas une. « Répète count fois » s'écrit for i in range(count):.

3) Un float dans range

half = 10 / 2               # 4.0 + rappel 01-2 : / donne TOUJOURS un float
for i in range(half):
    print(i)
# TypeError: 'float' object cannot be interpreted as an integer

range exige des entiers. Le coupable est presque toujours une division / en amont : utilise // (division entière) ou repense le calcul.

4) Modifier la variable de boucle en croyant piloter la boucle

for i in range(3):
    print(i)
    i = 100                # sans effet sur la suite !

Sortie : 0 1 2 (sur trois lignes). Au tour suivant, le for écrase i avec la valeur suivante du range — ta réassignation est jetée. Un for ne se pilote pas de l'intérieur ; si tu as besoin de sauter des valeurs à la main, c'est un while qu'il te faut.

5) Croire que break sort de TOUTES les boucles

for row in range(3):
    for col in range(3):
        if col == 1:
            break          # ne sort QUE de la boucle col
        print(row, col)

Sortie : 0 0, 1 0, 2 0 — la boucle row continue imperturbablement. break ne casse que la boucle la plus proche. (S'échapper de deux boucles d'un coup se fait proprement avec une fonction et return ⏩ niveau 4.)

6) continue avant la mise à jour d'un while

n = 0
while n < 5:
    if n == 2:
        continue           # saute le n += 1 → n reste 2 → boucle infinie
    print(n)
    n += 1

Le programme gèle à n == 2 (Ctrl+C, leçon 02-3). Dans un while, continue remonte DIRECTEMENT à la condition, en sautant tout ce qui suit — y compris la mise à jour. C'est le croisement le plus dangereux entre les leçons 02-3 et 02-4 : dans un for, le même continue serait inoffensif.

7. Lire les messages d'erreur

TypeError — l'entier qu'on ne peut pas parcourir

Combien ? Traceback (most recent call last):
  File "c:\Users\jtron\Claude\learnpython\loop.py", line 2, in <module>
    for i in count:
             ^^^^^
TypeError: 'int' object is not iterable
  1. Dernière ligne : is not iterable = « pas parcourable ». Le message donne le type coupable : 'int'. Un for a reçu un nombre nu.
  2. Au-dessus : les ^^^^^ soulignent count — c'est bien l'objet après in qui pose problème, pas le for lui-même.
  3. Correction quasi mécanique : envelopper dans range(...). Tu recroiseras is not iterable toute ta vie ; le réflexe est toujours « qu'est-ce que j'essaie de parcourir, et est-ce parcourable ? ».

TypeError — le float dans range

Traceback (most recent call last):
  File "c:\Users\jtron\Claude\learnpython\table.py", line 2, in <module>
    for i in range(half):
             ~~~~~^^^^^^
TypeError: 'float' object cannot be interpreted as an integer
  1. Dernière ligne : range voulait un entier, il a reçu un float.
  2. Le soulignement ~~~~~^^^^^^ désigne l'appel range(half) : la valeur fautive est half. Ligne du symptôme ≠ ligne de la cause (rappel 01-2) : remonte à la création de half — une division / y traîne sûrement.
  3. Deux corrections selon l'intention : 10 // 2 si tu voulais un entier de bout en bout, ou int(half) si le float est légitime par ailleurs.

Et comme toujours au niveau 2 : l'erreur la plus fréquente de la leçon — l'off-by-one — ne produit aucun traceback. Un résultat faux de 1 ou une itération de trop/de moins doit immédiatement te faire penser : bornes de range.

8. Exercices — faciles

Règles : nouveau fichier par exercice dans lessons/level-02-conditions-boucles/exercices/mes-reponses/, nommé ex-02-4-a.py, etc. Tape tout à la main. Prédis la sortie AVANT d'exécuter.

a) Sans exécuter, écris sur papier les valeurs EXACTES produites par chacun (première, dernière, combien), puis vérifie avec une boucle print :

range(4)
range(2, 7)
range(10, 0, -3)
range(3, 3)

b) Affiche les multiples de 3 de 3 à 30 inclus, sur une ligne (3 6 9 ... 30). Deux versions : une avec le pas de range, une avec range(3, 31) + un if + continue. Vérifie que les deux sorties sont identiques — surtout la présence du 30.

c) Demande une phrase et affiche le nombre de lettres e qu'elle contient. Contrôle : elle prefere le the vert → 8.

9. Exercices — moyens

a) Ce programme doit afficher la table de 7, « de 1 à 10 ». Il tourne sans erreur mais un client se plaint : il manque une ligne. Trouve le bug SANS exécuter, dis quelle ligne manque, corrige, et donne le range exact :

for i in range(1, 10):
    print(f"7 x {i} = {7 * i}")

b) Somme des nombres pairs de 1 à 100 (contrôle : 2550). Trois versions qui doivent donner le même résultat : (1) range avec pas de 2 ; (2) range(1, 101) + if ; (3) range(1, 101) + continue sur les impairs. Laquelle trouves-tu la plus lisible ? Note-le dans ton journal.

c) La pyramide : affiche

*
**
***
****
*****

avec un for et la multiplication de chaîne "*" * n (vue en 01-1 avec la bordure de la carte). Puis la version inversée (5 étoiles, puis 4, … puis 1) — attention au pas et aux bornes du range, écris première/dernière valeur avant de taper.

10. Exercices — difficiles

a) Affiche tous les nombres premiers entre 2 et 30 (un nombre premier n'est divisible que par 1 et lui-même). Sortie attendue : 2 3 5 7 11 13 17 19 23 29. Structure imposée : une boucle extérieure sur les candidats, une boucle intérieure qui cherche un diviseur, un break, et une variable bool (un « drapeau »).

11. Mini-projet lié — « table de multiplication à trous »

Écris quiz_tables.py : un quiz de 5 questions de multiplication tirées au hasard, avec score final. Session type :

=== Quiz des tables ===
Question 1 : 7 x 8 ? 56
Correct !
Question 2 : 3 x 9 ? 28
Faux : 3 x 9 = 27
...
Score final : 4/5

Contraintes : un for sur range(1, 6) pour les 5 questions ; les deux facteurs viennent de random.randint(1, 10) (présenté en leçon 02-3) ; un compteur de bonnes réponses (motif de 02-3) ; la réponse de l'utilisateur est convertie en int ; le message d'erreur donne la bonne réponse ; le nombre de questions vit dans UNE variable question_count en haut du fichier, utilisée à la fois par le range et par l'affichage du score (aucun 5 en dur en double — règle DRY de la leçon 01-1). Durée cible : 30–45 min.

Extension : à la fin, si le score est parfait, affiche un message spécial ; puis (plus dur) transforme le tout : la MÊME question est reposée tant que la réponse est fausse (un while de validation DANS le for — l'imbrication des deux leçons).

12. Correction / méthode de correction

Les solutions détaillées sont dans solutions/serie-02-4-solutions.md.

Méthode : (1) finis TOUS les exercices avant d'ouvrir les solutions ; (2) compare le raisonnement, pas seulement le code ; (3) pour chaque écart, une ligne de journal : « je pensais X, en fait Y » ; (4) refais de tête l'exercice raté, le lendemain.

13. À retenir

14. Questions de révision

  1. Quelles valeurs produit range(3, 12, 3) ? Première, dernière, combien ?
  2. Pourquoi range(1, 10) ne suffit-il pas pour « de 1 à 10 » ? Quelle est la convention, et quel est son avantage ?
  3. Quelle différence de comportement entre break et continue ? Donne un usage légitime de chacun.
  4. Que se passe-t-il si on réassigne la variable de boucle dans le bloc d'un for ?
  5. Dans deux boucles imbriquées, de laquelle break sort-il ?
  6. Pourquoi continue est-il plus dangereux dans un while que dans un for ?
  7. for ou while : quelle question poser pour choisir ? Donne un exemple de chaque côté.
  8. Que produit range(5, 5) ? Et range(1, 10, -1) ? Est-ce une erreur ?

15. Checklist de compréhension

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-02-conditions-boucles/exercices/mes-reponses/
git commit -m "exercises: complete lesson 02-4 for and range"

Puis, si c'est ta fin de session : entrée de journal + git push.