Leçon 02-3 — while : répéter tant que
1. Objectif
À la fin de cette leçon tu sauras : écrire une boucle while avec une condition d'arrêt correcte ; construire les deux motifs fondamentaux (compteur, accumulateur) ; valider une saisie utilisateur en redemandant tant qu'elle est invalide ; et surtout provoquer volontairement une boucle infinie, la reconnaître, l'arrêter avec Ctrl+C et la diagnostiquer — le rite de passage de tout programmeur.
2. Pourquoi c'est important
Répéter est la moitié du métier d'un programme : redemander un mot de passe, recalculer jusqu'à convergence, faire tourner un menu jusqu'à « quitter », rejouer une partie. while est la boucle des répétitions dont on ne connaît pas le nombre à l'avance — c'est l'utilisateur, ou le calcul, qui décide quand ça s'arrête. (Quand le nombre de tours est connu, on prendra for ⏩ leçon 02-4.) C'est aussi la structure la plus dangereuse vue jusqu'ici : une condition mal pensée et ton programme ne s'arrête jamais. Apprendre à ne pas en avoir peur, c'est l'objectif caché de cette leçon.
3. Explication simple
while se lit « TANT QUE la condition est vraie, refais le bloc » :
countdown = 3
while countdown > 0:
print(countdown)
countdown = countdown - 1
print("Go !")
Sortie :
3
2
1
Go !
Le film, tour par tour : Python évalue countdown > 0. Vrai (3 > 0) → il exécute le bloc (affiche 3, décrémente à 2), puis remonte à la condition. Vrai (2 > 0) → bloc. Vrai (1 > 0) → bloc. Faux (0 > 0) → il saute le bloc et continue après la boucle. La syntaxe est celle d'un if (condition, :, bloc indenté) — la seule différence est le retour en haut.
Trois pièces à repérer dans TOUTE boucle while :
- L'initialisation avant la boucle (
countdown = 3). - La condition testée à chaque tour (
countdown > 0). - La mise à jour dans le bloc (
countdown = countdown - 1) — la ligne qui fait que la condition finira par devenir fausse.
Enlève la pièce 3, et la condition reste vraie pour l'éternité : c'est la boucle infinie.
4. Explication approfondie
a) La condition est testée AVANT chaque tour, jamais pendant. Deux conséquences. Si elle est fausse dès le départ, le bloc ne s'exécute zéro fois (une boucle qui ne tourne pas est légale). Et si elle devient fausse au milieu du bloc, le tour en cours va jusqu'au bout — la sortie n'a lieu qu'au prochain passage en haut.
b) Les deux motifs à connaître par cœur.
- Le compteur :
n = 0avant,n += 1dans le bloc — compte les tours (essais, ventes, années…). - L'accumulateur :
total = 0avant,total += valeurdans le bloc — agrège un résultat (somme, produit avectotal = 1, texte avec"").
Les deux se combinent en permanence (compter les essais ET cumuler un total). Erreur classique : initialiser DANS la boucle — la variable est remise à zéro à chaque tour et le motif meurt (⏩ disséqué en 02-5).
c) La boucle infinie, honnêtement. Tu vas en écrire, souvent, pendant des années — ce n'est pas une honte, c'est une propriété du while. Ce qui compte :
- La reconnaître : le terminal « gèle » (plus de prompt, rien ne se passe), ou défile sans fin, ou le ventilateur s'emballe.
- L'arrêter :
Ctrl+Cdans le terminal. Python lèveKeyboardInterruptet te montre la ligne où il tournait — un traceback qui est un cadeau : il pointe la boucle coupable (section 7). - La diagnostiquer : relire les trois pièces. La mise à jour existe-t-elle ? Est-elle DANS le bloc (indentation !) ? Va-t-elle dans le bon sens (un
n += 1avecwhile n > 0:ne s'arrêtera jamais) ? La condition peut-elle seulement devenir fausse ?
d) Le motif « validation de saisie » — la vraie puissance du while :
code = ""
while code != "1234":
code = input("Code PIN ? ")
print("Coffre ouvert !")
Ici, PAS de compteur : c'est l'utilisateur qui fait évoluer la condition. Note l'astuce d'amorçage : code = "" donne à la condition une première valeur à tester (et "" est différent de "1234", donc on entre dans la boucle). Oublie l'amorçage → NameError ; mets l'input() AVANT la boucle seulement → la même réponse est retestée à l'infini (section 6).
e) while True existe — une boucle volontairement infinie dont on sort par un break au milieu. Tu la croiseras dans du vrai code ; elle est propre et idiomatique. ⏩ break est au programme de la leçon 02-4 : ici on s'entraîne d'abord à écrire des conditions d'arrêt correctes, le muscle fondamental.
Difficulté honnête : c'est LA leçon charnière du niveau (le README du niveau te l'annonçait). La syntaxe tient en une ligne ; la compétence — penser « qu'est-ce qui rend cette condition fausse, et quand ? » — demande des dizaines de boucles écrites. Prévois 3–4 sessions, fais TOUS les exercices, et provoque la boucle infinie de l'exercice 8-c sans culpabiliser : c'est le but.
5. Exemples commentés
Exemple 1 — le compte à rebours (section 3 — tape-le vraiment).
Exemple 2 — accumulateur : la somme de 1 à 100
total = 0 # accumulateur, initialisé AVANT la boucle
n = 1 # compteur
while n <= 100:
total += n # accumule
n += 1 # met à jour la condition — la pièce vitale
print(total)
Sortie : 5050. Deux variables, deux rôles : n avance, total engrange. (Gauss l'a calculé de tête à 9 ans ; nous, on a une boucle.)
Exemple 3 — validation de saisie (le coffre de la section 4-d).
Session complète :
Code PIN ? 0000
Code PIN ? 9999
Code PIN ? 1234
Coffre ouvert !
Tant que la réponse est mauvaise, la question revient — le programme est inusable par l'utilisateur, c'est exactement ce qu'on veut.
Exemple 4 — compteur + validation : les essais
secret = "python"
guess = ""
attempts = 0
while guess != secret:
guess = input("Mot de passe ? ")
attempts += 1 # compte CHAQUE essai, bon ou mauvais
print(f"Trouve en {attempts} essais")
Session :
Mot de passe ? java
Mot de passe ? ruby
Mot de passe ? python
Trouve en 3 essais
Le attempts += 1 est dans la boucle mais HORS de tout if : le bon essai compte aussi. (Mets-le au mauvais endroit et le total ment de 1 — teste toujours la victoire au premier coup.)
Exemple 5 — accumulateur avec sentinelle : la caisse
total = 0
price = -1 # amorçage : n'importe quoi sauf 0
while price != 0:
price = float(input("Prix (0 pour finir) ? "))
total += price # 0 ajouté au dernier tour : inoffensif ici
print(f"Total : {total:.2f} EUR")
Session :
Prix (0 pour finir) ? 12.5
Prix (0 pour finir) ? 7.25
Prix (0 pour finir) ? 0
Total : 19.75 EUR
Le 0 est une sentinelle : une valeur spéciale qui signifie « stop ».
Exemple 6 — la boucle qui ne tourne jamais
n = 10
while n < 10:
print("jamais affiche")
print("fin")
Sortie : fin. Condition fausse au premier test → zéro tour, pas d'erreur. Si ta boucle « ne fait rien », affiche les valeurs de la condition juste avant le while — neuf fois sur dix, elle était fausse d'entrée.
6. Erreurs fréquentes
1) Oublier la mise à jour → boucle infinie
n = 1
while n > 0:
n = n + 1 # n GRANDIT : n > 0 restera vrai pour toujours
Le classique absolu — ici la mise à jour existe mais va dans le mauvais sens. Symptôme : le terminal gèle. Remède immédiat : Ctrl+C. Diagnostic : « qu'est-ce qui rend n > 0 faux ? » — réponse : rien, jamais.
2) L'input() hors de la boucle
answer = input("Continuer ? (oui/non) ")
while answer == "oui":
print("On continue !") # redemande jamais : answer ne change plus
Si l'utilisateur tape oui, l'écran se remplit de On continue ! à l'infini — la question n'est posée qu'UNE fois, la condition reteste la même valeur. Correction : re-poser la question DANS le bloc. (C'est l'erreur n°1 du jeu de devinette — la fiche projet te prévient aussi.)
3) La mise à jour mal indentée
n = 3
while n > 0:
print(n)
n = n - 1 # HORS du bloc : jamais exécutée pendant la boucle
Une seule dé-indentation et la ligne vitale sort de la boucle → infinie. Pas de message d'erreur : l'indentation est légale, juste fausse. Relis toujours le bloc en te demandant « qu'est-ce qui est VRAIMENT dedans ? ».
4) = au lieu de == dans la condition
n = 10
while n = 10:
print("boucle")
# SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
Même protection qu'au if (leçon 02-1) : Python refuse et suggère. Ici il te sauve d'une boucle infinie en prime.
5) Comparer la saisie sans convertir
target = 100
n = input("Un nombre ? ") # "42" — une str !
while n < target:
...
# TypeError: '<' not supported between instances of 'str' and 'int'
Le réflexe 01-2/02-1 vaut aussi pour les conditions de boucle : n = int(input(...)). Et avec != au lieu de <, pas d'erreur du tout — "100" != 100 est toujours vrai → boucle infinie silencieuse. Le pire cumul des deux niveaux.
6) Condition d'arrêt inversée
code = ""
while code == "1234": # faux dès le départ : on voulait !=
code = input("Code PIN ? ")
print("Coffre ouvert !")
Affiche Coffre ouvert ! sans poser une seule question : la condition est fausse au premier test, zéro tour. while garde le bloc tant que c'est vrai — la condition exprime « on continue », pas « on s'arrête ». Astuce : dis ta boucle à voix haute : « tant que le code est bon, redemande » — tu entends l'absurdité.
7. Lire les messages d'erreur
KeyboardInterrupt — TON arme contre la boucle infinie. Programme forever.py :
n = 1
while n > 0:
n = n + 1
Lancé, il gèle. On appuie sur Ctrl+C :
Traceback (most recent call last):
File "c:\Users\jtron\Claude\learnpython\forever.py", line 3, in <module>
n = n + 1
^
KeyboardInterrupt
- Dernière ligne :
KeyboardInterrupt— pas un bug du programme, c'est TOI qui as interrompu. Aucune honte : c'est l'outil prévu pour ça. - Au-dessus : le fichier et la ligne où Python était au moment précis de l'interruption — ici la ligne 3, en plein cœur de la boucle. C'est le cadeau du traceback : il te montre OÙ ça tournait. Si tu interromps un programme « gelé » et que la ligne pointée est dans un
while, ta boucle infinie est localisée. - Diagnostic ensuite : les trois pièces (init, condition, mise à jour) — ici
nmonte,n > 0ne peut que rester vrai.
Un KeyboardInterrupt peut pointer n'importe laquelle des lignes du bloc (celle en cours d'exécution à l'instant du Ctrl+C) — relance et interromps encore : la ligne peut changer, la boucle désignée est la même.
Et le gel SANS traceback ? Si le terminal semble figé juste après un lancement, vérifie d'abord que ce n'est pas un input() qui attend sagement ta réponse (pas de boucle du tout — regarde si un prompt est affiché). Ctrl+C fonctionne dans les deux cas ; sur un input(), le traceback pointera la ligne de l'input().
8. Exercices — faciles
Règles : nouveau fichier par exercice dans
lessons/level-02-conditions-boucles/exercices/mes-reponses/, nomméex-02-3-a.py, etc. Tape tout à la main. Prédis la sortie AVANT d'exécuter.
a) Compte à rebours de 10 à 1, puis Decollage !. Une boucle while, une seule variable. Vérifie que 10 ET 1 s'affichent (pas 11 valeurs, pas 9 — écris les premières et dernières valeurs sur papier d'abord).
b) Demande un entier n et affiche la somme 1 + 2 + ... + n avec le motif compteur + accumulateur. Contrôle : pour n = 100, tu dois obtenir 5050.
c) (rite de passage) Écris VOLONTAIREMENT forever.py, la boucle infinie de la section 7. Lance-la. Constate le gel. Arrête-la avec Ctrl+C. Lis le traceback à voix haute : type d'erreur, fichier, ligne. Note dans ton journal la sensation — dans un mois ce sera un réflexe tranquille.
9. Exercices — moyens
a) Ce programme est censé afficher 3 2 1 Partez ! mais il gèle. SANS l'exécuter : lequel des trois éléments (initialisation / condition / mise à jour) est cassé, et pourquoi ? Corrige, puis vérifie :
n = 3
while n > 0:
print(n)
n = n - 1
print("Partez !")
b) Validation stricte : demande une note entre 0 et 20, et REDEMANDE tant que la saisie est hors bornes (utilise or, leçon 02-2). À la fin, affiche Note enregistree : X. Contraintes : l'amorçage doit garantir l'entrée dans la boucle (pense à une première valeur forcément invalide, par exemple -1) ; teste avec la séquence de saisies 25, -3, 12.
c) Les grains de riz : on pose 1 grain le jour 1, et chaque jour le total double. Affiche le premier jour où le total dépasse 1000 grains, et le total ce jour-là. Contrôle : Jour 11 : 1024 grains. Contrainte : c'est la CONDITION qui décide de l'arrêt (aucun 11 en dur dans le code — tu ne connais pas la réponse à l'avance, c'est tout l'intérêt du while).
10. Exercices — difficiles
a) L'algorithme d'Euclide calcule le PGCD (plus grand diviseur commun) de deux entiers en répétant UNE idée : remplacer la paire (a, b) par (b, a % b) tant que b n'est pas nul ; quand b tombe à zéro, a est le PGCD. Écris gcd.py : demande deux entiers, affiche leur PGCD. Contrôles : PGCD(48, 36) = 12 ; PGCD(17, 5) = 1 ; PGCD(100, 10) = 10.
- Indice 1 : la condition du
whileest donnée mot pour mot dans l'énoncé. - Indice 2 : il faut remplacer
aetben même temps — te souviens-tu de l'échangea, b = b, ade la leçon 01-1 ? Même syntaxe, autre droite. - Indice 3 : le corps de la boucle tient en une ligne :
a, b = b, a % b. Après la boucle, le résultat est dansa. Déroule PGCD(48, 36) à la main sur papier pour te convaincre : (48,36) → (36,12) → (12,0) → réponse 12.
11. Mini-projet lié — le jeu de devinette
Le projet-roi du while : l'ordinateur tire un nombre entre 1 et 100, tu devines, il répond « trop grand / trop petit » et compte tes essais. Fiche complète (cahier des charges, étapes, pièges) : projects/mini-projects/03-jeu-devinette.md. Durée cible : 1–2 h. Suis les étapes de la fiche dans l'ordre — la n°1 se fait sans boucle, tu as tout depuis la leçon 02-1.
Il te manque une seule brique : le tirage au sort.
import random # en tout premier, en haut du fichier
secret = random.randint(1, 100) # un entier au hasard, bornes INCLUSES
import random charge la boîte à outils du hasard (⏩ les imports en détail au niveau 5 — pour l'instant, recopie ces deux lignes telles quelles) ; random.randint(1, 100) renvoie un entier différent à chaque lancement. Attention, ses DEUX bornes sont incluses — contrairement à range que tu verras en 02-4. Les erreurs fréquentes de la fiche sont exactement celles de ta section 6 (l'input() hors boucle en tête) : tu es armé.
12. Correction / méthode de correction
Les solutions détaillées sont dans solutions/serie-02-3-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
while condition:répète le bloc tant que la condition est vraie, testée avant chaque tour (zéro tour possible).- Trois pièces obligatoires : initialisation avant, condition, mise à jour dans le bloc. La mise à jour absente, mal indentée ou dans le mauvais sens = boucle infinie.
- Deux motifs à connaître par cœur : compteur (
n += 1) et accumulateur (total += valeur) — initialisés AVANT la boucle. - Validation de saisie : amorcer la variable,
input()DANS la boucle. - Boucle infinie → Ctrl+C →
KeyboardInterrupt: le traceback pointe la ligne où ça tournait. C'est un outil, pas un échec. - La condition dit « on continue », pas « on s'arrête » — dis-la à voix haute.
input()non converti +<=TypeError; +!== boucle infinie silencieuse.random.randint(1, 100): entier au hasard, deux bornes incluses.
14. Questions de révision
- Quelles sont les trois pièces de toute boucle
while? Que se passe-t-il si chacune manque ? - La condition d'un
whileest-elle re-testée au milieu d'un tour ? Quelle conséquence pratique ? - Combien de fois s'exécute le bloc si la condition est fausse dès le début ? Est-ce une erreur ?
- Pourquoi l'
input()d'une boucle de validation doit-il être DANS le bloc ? Quel est le symptôme sinon ? - Que fait Ctrl+C sur un programme gelé, et que t'apprend le traceback obtenu ?
- Quelle différence de symptôme entre
while n < target:etwhile n != target:quandnest unestrjamais convertie ? - Qu'est-ce qu'une valeur sentinelle ? Donne l'exemple de la caisse.
- Dans l'algorithme d'Euclide, pourquoi faut-il l'assignation simultanée
a, b = b, a % bplutôt que deux lignesa = bpuisb = a % b?
15. Checklist de compréhension
- J'écris un compte à rebours
whilejuste du premier coup (bornes comprises). - Je connais les deux motifs compteur/accumulateur et je sais POURQUOI l'initialisation est avant la boucle.
- J'ai provoqué une boucle infinie exprès, je l'ai arrêtée avec Ctrl+C et j'ai lu le traceback sans paniquer.
- Devant un programme gelé, je vérifie d'abord :
input()en attente ou vraie boucle infinie ? - Je sais écrire une validation de saisie qui redemande tant que c'est invalide, avec amorçage correct.
- Je repère en relecture une mise à jour mal indentée (hors du bloc).
- Le jeu de devinette tourne : partie complète, compteur d'essais exact, jamais de boucle infinie.
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-3 while loops"
Puis, si c'est ta fin de session : entrée de journal + git push.