Objectifs du chapitre
À la fin de ce chapitre, vous serez capable de:
- écrire une expression booléenne avec les opérateurs de comparaison, en distinguant sans hésiter l'affectation
=de la comparaison==; - écrire un
if, unif/elseet une chaîneif/elif/else, et dire exactement quelles lignes s'exécutent dans chaque cas; - reconnaître, lire et corriger une
IndentationErroret uneSyntaxErrordues au bloc et au deux-points; - combiner des conditions avec
and,oretnot, les parenthéser correctement et les réécrire avec les lois de De Morgan; - expliquer l'évaluation paresseuse, la comparaison enchaînée
0 <= x <= 10et la notion de valeur «vraie» ou «fausse» en Python; - comparer deux nombres à virgule avec une tolérance, et construire un programme à partir d'une table de décision dont vous aurez vérifié qu'elle est complète et sans recouvrement.
Convention de lecture. Dans tout le chapitre, un bloc marqué python contient un programme complet, et le bloc marqué text qui le suit contient exactement ce que Python a affiché lorsque nous l'avons exécuté. Aucune sortie de ce chapitre n'a été écrite à la main. Les messages d'erreur reproduits sont ceux de Python 3.13; depuis la version 3.10, l'interpréteur est devenu nettement plus bavard, et un Python plus ancien vous donnera les mêmes erreurs avec des phrases plus sèches.
Pourquoi un programme doit choisir
Un programme qui ne sait pas choisir
Tous les programmes du chapitre 1 avaient un défaut commun, et vous ne l'avez peut-être pas remarqué parce qu'ils étaient courts: ils faisaient toujours la même chose. L'interpréteur lisait la première ligne, puis la deuxième, puis la troisième, jusqu'à la fin. Changer les valeurs des variables changeait les nombres affichés, jamais le chemin suivi.
Voici un tel programme. Il calcule le prix d'un billet de train pour un enfant.
# Le prix d'un billet de train, sans condition
prix_plein = 24.00
age = 12
prix = prix_plein / 2 # demi-tarif enfant
print("Age:", age)
print("Prix a payer:", prix, "CHF")
Age: 12
Prix a payer: 12.0 CHF
Le résultat est juste. Mais remplacez 12 par 40 en gardant le reste: le programme annonce toujours CHF 12.00, parce que la ligne prix = prix_plein / 2 est exécutée quoi qu'il arrive. Ce programme ne calcule pas un prix, il applique une règle unique. Dès qu'un tarif dépend d'une catégorie, d'un seuil ou d'une case cochée, la suite linéaire d'instructions ne suffit plus.
C'est le cas de presque tous les programmes utiles. Un correcteur automatique décide si une réponse est acceptée; un capteur déclenche une alarme au-delà d'un seuil; un formulaire refuse une adresse mal formée; une application bancaire refuse un retrait supérieur au solde. Dans chaque cas, le programme doit choisir entre deux suites d'instructions au moins, et ce choix dépend d'une information qui n'est connue qu'au moment de l'exécution.
L'idée de branche
Le mécanisme qui permet ce choix s'appelle une structure conditionnelle. Son principe est d'une simplicité remarquable: on pose une question dont la réponse est oui ou non, et selon la réponse on exécute un groupe d'instructions ou un autre. Le programme précédent devient:
prix_plein = 24.00
age = 12
if age < 16:
prix = prix_plein / 2
else:
prix = prix_plein
print("Age:", age)
print("Prix a payer:", prix, "CHF")
Age: 12
Prix a payer: 12.0 CHF
La sortie est la même qu'avant, ce qui est rassurant, mais le programme n'est plus le même: avec age = 40, il affiche Prix a payer: 24.0 CHF. Une des deux lignes prix = … a été exécutée, l'autre a été purement et simplement sautée. C'est là toute la nouveauté du chapitre: pour la première fois, une ligne de votre programme peut ne jamais s'exécuter.
Deux conséquences méritent d'être posées tout de suite, parce qu'elles gouvernent tout le reste du cours.
D'abord, le texte du programme n'est plus l'exécution du programme. Vous écrivez dix lignes, six seulement sont exécutées, et lesquelles dépend des données. Un débutant qui lit son code comme une recette linéaire se trompera systématiquement; il faut apprendre à lire un programme en se demandant, à chaque bifurcation, «et si la condition était fausse?». Nous prendrons l'habitude de tracer l'exécution, c'est-à-dire de suivre le chemin réellement pris pour un jeu de valeurs donné.
Ensuite, tester un programme conditionnel demande plusieurs jeux de valeurs. Le programme du billet fonctionne pour age = 12; cela ne dit rien du cas age = 40, ni du cas limite age = 16, ni du cas absurde age = -3. Un programme à deux branches se teste avec au moins deux valeurs, un programme à quatre branches avec au moins quatre. Cette discipline, que l'on appelle la couverture des cas, vaut dès aujourd'hui, et nous la formaliserons à la fin du chapitre avec les tables de décision.
Le type bool
Deux valeurs, un type
Avant d'écrire des conditions, il faut savoir ce qu'est une condition. Le chapitre 1 a présenté les types int, float et str. En voici un quatrième, et c'est le plus petit de tous: il ne contient que deux valeurs.
print(3 < 5)
print(3 > 5)
print(type(3 < 5))
print(True)
print(False)
True
False
<class 'bool'>
True
False
True et False sont deux valeurs du langage, au même titre que 42 ou "bonjour". Elles s'écrivent avec une majuscule initiale et sans guillemets: True est le booléen vrai, tandis que "True" serait une chaîne de cinq caractères et true une erreur (NameError: name 'true' is not defined, car Python chercherait une variable de ce nom). Le nom du type, bool, rend hommage à George Boole, qui a formalisé au milieu du XIXᵉ siècle l'algèbre de ces deux valeurs.
L'observation décisive est que 3 < 5 est une expression, exactement comme 3 + 5. On peut l'écrire seule, l'afficher, la ranger dans une variable, et Python lui donne une valeur — ici True — avant d'en faire quoi que ce soit. Une condition n'est pas une construction magique du langage: c'est un calcul dont le résultat se trouve être un booléen.
Les opérateurs de comparaison
Six opérateurs produisent des booléens à partir de deux valeurs comparables. Ils s'écrivent presque comme en mathématiques, à deux exceptions près que le clavier impose.
| Opérateur | Se lit | Exemple vrai | Exemple faux |
|---|---|---|---|
== | est égal à | 4.5 == 4.5 | 4.5 == 4 |
!= | est différent de | 4.5 != 4 | 4.5 != 4.5 |
< | est strictement inférieur à | 3 < 4 | 4 < 4 |
<= | est inférieur ou égal à | 4 <= 4 | 5 <= 4 |
> | est strictement supérieur à | 5 > 4 | 4 > 4 |
>= | est supérieur ou égal à | 4 >= 4 | 3 >= 4 |
Les deux exceptions sont l'égalité, qui s'écrit avec deux signes égal, et l'inégalité, qui s'écrit != parce que le signe n'existe pas sur un clavier. Notez aussi que <= s'écrit dans cet ordre et sans espace au milieu: =< est une erreur de syntaxe, et < = aussi.
note = 4.5
print(note == 4.5)
print(note != 4.0)
print(note >= 4)
print(note < 4)
print("Alice" == "alice")
print(3 == 3.0)
True
True
True
False
False
True
Trois lignes de cette sortie méritent un commentaire. note >= 4 vaut True alors que note est un float et 4 un int: Python compare les valeurs numériques, pas les types, et 4 est converti en 4.0 pour l'occasion. "Alice" == "alice" vaut False: la comparaison de chaînes est sensible à la casse, et une majuscule est un caractère différent de la minuscule correspondante. Enfin 3 == 3.0 vaut True, ce qui confirme le premier point: un entier et un flottant de même valeur sont égaux, même si type(3) et type(3.0) diffèrent.
Sur les chaînes, < et > comparent dans l'ordre alphabétique — plus précisément dans l'ordre des codes des caractères, si bien que "Zurich" < "aarau" vaut True, toutes les majuscules venant avant toutes les minuscules. Nous y reviendrons au chapitre 5; d'ici là, comparez des chaînes uniquement avec == et !=.
Quelle est la valeur et le type de l'expression 7 != 7.0 en Python?
Une condition est une valeur comme une autre
Puisqu'une comparaison est une expression, on peut ranger son résultat dans une variable, et c'est souvent une excellente idée: le nom donné à la variable documente la condition.
note = 4.5
reussi = note >= 4
print(reussi)
print(type(reussi))
True
<class 'bool'>
Un nom bien choisi — reussi, majeur, solde_suffisant, fichier_vide — transforme une ligne technique en une phrase lisible. Comparez if note >= 4 and presence >= 0.8 and rendu_effectue: avec if reussi and assidu and rendu_effectue:. La seconde version se relit à voix haute, et surtout elle sépare la définition des critères de leur combinaison, ce qui permet d'en corriger un sans toucher au reste.
L'instruction if
La forme la plus simple
La forme minimale d'une structure conditionnelle est un if seul: un bloc d'instructions qui s'exécute si la condition est vraie, et qui est sauté sinon.
temperature = -3.0
if temperature < 0:
print("Attention: risque de verglas.")
print("Fin du bulletin.")
Attention: risque de verglas.
Fin du bulletin.
Avec temperature = 5.0, la première ligne disparaît de la sortie et il ne reste que Fin du bulletin.. Observez la forme de l'instruction, caractère par caractère, car chacun de ses éléments est obligatoire:
- le mot-clé
if, en minuscules; - la condition, une expression booléenne (ici
temperature < 0), sans parenthèses obligatoires — Python les accepte mais elles sont inutiles; - le deux-points
:qui termine la ligne, et qui annonce «un bloc suit»; - le bloc, c'est-à-dire une ou plusieurs lignes indentées de quatre espaces, qui appartiennent au
if; - la suite du programme, revenue à l'indentation précédente, qui s'exécute dans tous les cas.
if … else
Le if seul ne dit rien de ce qu'il faut faire quand la condition est fausse. Le mot-clé else — «sinon» — ajoute cette seconde branche.
note = 4.5
if note >= 4:
print("Suffisant")
else:
print("Insuffisant")
Suffisant
else n'a pas de condition: il ramasse tout ce que le if n'a pas pris, et son en-tête se réduit donc au mot-clé suivi du deux-points. Les deux branches sont exclusives — jamais les deux — et exhaustives — jamais aucune. Autrement dit, ce programme affiche toujours exactement une ligne, quelle que soit la valeur de note. C'est une garantie précieuse, et nous verrons plus loin comment la conserver quand il y a plus de deux cas.
if … elif … else
Trois situations ou davantage se traitent avec elif, contraction de else if. Chaque elif porte sa propre condition et n'est examiné que si toutes les conditions précédentes se sont révélées fausses. Voici, en trois cas, une version volontairement simplifiée du barème de mentions du cours; nous l'écrirons en entier à la fin du chapitre.
note = 4.5
if note < 4:
mention = "insuffisant"
elif note < 5:
mention = "suffisant"
else:
mention = "bien"
print("Note", note, ":", mention)
Note 4.5 : suffisant
Le point qui échappe le plus souvent aux débutants est que elif note < 5 ne signifie pas «si la note est inférieure à 5», mais «si elle n'est pas inférieure à 4 et qu'elle est inférieure à 5». La condition d'un elif est implicitement complétée par la négation de toutes celles qui précèdent. C'est ce qui permet d'écrire des intervalles avec une seule borne par test: on n'écrit jamais elif note >= 4 and note < 5, la première moitié est acquise.
L'ordre des tests est donc significatif: le premier qui réussit gagne et les suivants ne sont même pas évalués. Inverser deux lignes d'une chaîne elif change le programme, et souvent le casse silencieusement, sans erreur, avec un résultat simplement faux.
La figure 2.1 montre pourquoi on parle de branches: le flot d'exécution descend, rencontre un losange, part d'un côté ou de l'autre, et les chemins se rejoignent en bas. Trois propriétés se lisent directement sur le dessin, et ce sont celles qu'il faut vérifier sur toute structure conditionnelle que vous écrivez: un seul chemin est emprunté par exécution, aucun chemin ne se perd puisque le else final ramasse le reste, et tous les chemins se rejoignent sur la suite du programme.
Un programme contient: si x est strictement positif, afficher «positif»; sinon, si x est strictement supérieur à 1, afficher «plus grand que un»; sinon afficher «negatif ou nul». Avec x valant 2, qu'affiche-t-il?
Cette dernière remarque vaut qu'on s'y arrête: une branche inatteignable est un bug parfaitement silencieux. Le programme s'exécute, n'affiche aucune erreur, et une partie de votre code n'est jamais utilisée. La règle pratique est de placer les conditions de la plus restrictive à la plus générale, et, quand les tests portent sur un seuil numérique, de les ordonner du même sens que l'axe des nombres, comme dans l'exemple des notes.
Choisissez les deux opérandes et l'opérateur de comparaison. La condition est évaluée comme Python l'évaluerait, et la ligne réellement exécutée est mise en évidence dans le programme. Cherchez les réglages pour lesquels == et >= ne donnent pas le même verdict, et ceux pour lesquels ils coïncident.
L'explorateur ci-dessus isole le seul mécanisme qui compte ici: une comparaison entre deux valeurs produit un booléen, et ce booléen choisit la ligne exécutée. Faites glisser les curseurs et observez trois choses. D'abord, note et seuil égaux distinguent brutalement les six opérateurs: ==, <= et >= donnent True, les trois autres False. Ensuite, < et <= ne diffèrent que sur cette valeur unique — c'est exactement là que se logent les erreurs de bord, et c'est pour cela qu'on teste toujours un seuil avec la valeur qui vaut le seuil. Enfin, quel que soit le réglage, une ligne est mise en évidence et une seule: la structure if/else ne laisse aucune exécution sans branche.
L'indentation est de la syntaxe, pas du style
Ce que l'indentation décide
Dans la plupart des langages, un bloc est délimité par des accolades et l'indentation n'est qu'une convention de présentation: elle aide l'œil, le compilateur l'ignore. En Python, l'indentation est la seule marque du bloc. Elle n'est pas un style, c'est de la syntaxe, au même titre que le deux-points ou les parenthèses. Déplacer une ligne de quatre espaces change le programme.
Voici deux programmes qui ne diffèrent que par l'indentation de leur cinquième ligne.
solde = 100.0
retrait = 150.0
if retrait <= solde:
solde = solde - retrait
print("Retrait effectue.")
print("Solde:", solde, "CHF")
Solde: 100.0 CHF
solde = 100.0
retrait = 150.0
if retrait <= solde:
solde = solde - retrait
print("Retrait effectue.")
print("Solde:", solde, "CHF")
Retrait effectue.
Solde: 100.0 CHF
Le second programme annonce un retrait qui n'a pas eu lieu. Le solde, lui, est resté à CHF 100.00 dans les deux cas, ce qui prouve que la ligne de calcul, elle, est bien restée dans le bloc. Aucune erreur n'a été signalée: les deux programmes sont syntaxiquement corrects, et seul le second est faux. C'est la forme la plus dangereuse de bug, celle qui ne se voit qu'à la lecture de la sortie.
Trois erreurs réelles
L'indentation produit des messages d'erreur très reconnaissables. Les voici, tels qu'ils sont apparus — le chemin complet du fichier étant toujours remplacé par son seul nom.
Premier cas: le bloc manque. Le deux-points annonce un bloc, et rien n'est indenté derrière.
temperature = -3.0
if temperature < 0:
print("Attention: risque de verglas.")
File "verglas.py", line 3
print("Attention: risque de verglas.")
^^^^^
IndentationError: expected an indented block after 'if' statement on line 2
Le message est remarquablement précis: il dit ce qu'il attendait (an indented block), après quelle instruction (after 'if' statement) et à quelle ligne se trouvait cette instruction (on line 2), tout en pointant la ligne 3 où le bloc aurait dû commencer. Notez que IndentationError est une sorte de SyntaxError: le fichier n'est pas exécuté du tout.
Deuxième cas: le deux-points manque. C'est l'oubli le plus fréquent des premières semaines, parce que rien, dans l'habitude mathématique, ne prépare à terminer une condition par un signe de ponctuation.
temperature = -3.0
if temperature < 0
print("Attention: risque de verglas.")
File "verglas2.py", line 2
if temperature < 0
^
SyntaxError: expected ':'
Le curseur se place après le dernier caractère de la condition, exactement là où le deux-points devait être, et le message tient en deux mots. Un Python antérieur à 3.10 affichait simplement SyntaxError: invalid syntax en pointant la ligne suivante, ce qui a coûté beaucoup de temps à beaucoup de gens.
Troisième cas: une indentation qui ne correspond à rien. Une ligne indentée sans en-tête qui la commande.
temperature = -3.0
print("Bulletin meteo")
print("Temperature:", temperature)
File "bulletin.py", line 3
print("Temperature:", temperature)
IndentationError: unexpected indent
Unexpected indent: «indentation inattendue». Ces deux mots suffisent à diagnostiquer. Retenez la symétrie des deux messages: expected an indented block signifie qu'il manque une indentation, unexpected indent qu'il y en a une de trop.
Voici, en désordre, les huit lignes d'un programme qui affiche la mention correspondant à une note. Remettez-les dans l'ordre. L'indentation n'apparaît pas dans la liste: c'est à vous de savoir quelles lignes forment un bloc.
Glissez les éléments pour les mettre dans le bon ordre
- mention = 'suffisant'
- if note < 4:
- else:
- mention = 'bien'
- elif note < 5:
- mention = 'insuffisant'
- print(mention)
- note = 4.5
Conditions imbriquées
Imbriquer un if dans un if
Un bloc peut contenir n'importe quelle instruction, y compris une autre structure conditionnelle. On parle alors de conditions imbriquées (nested). Chaque niveau d'imbrication ajoute quatre espaces d'indentation.
L'imbrication se justifie lorsque la question intérieure n'a de sens que si la question extérieure a reçu une certaine réponse.
note = 7.0
# Ici l'imbrication est justifiee: la question interieure
# n'a de sens que si la note est valide.
if 1 <= note <= 6:
if note >= 4:
print("Note valide, suffisante.")
else:
print("Note valide, insuffisante.")
else:
print("Note invalide:", note)
Note invalide: 7.0
Demander si une note est suffisante n'a aucun sens tant qu'on ne sait pas si c'est une note. La structure du programme reflète alors la structure du raisonnement: d'abord la validité, ensuite, et seulement ensuite, la comparaison au seuil. Un lecteur voit d'un coup d'œil que les deux messages «valide» sont sous la même garde.
Notez au passage l'écriture 1 <= note <= 6, qui se lit comme en mathématiques; nous y consacrerons une section entière plus bas.
Quand un if imbriqué doit devenir un elif
L'imbrication a mauvaise réputation, et elle la mérite quand elle ne sert à rien. Voici la même classification écrite deux fois. D'abord en imbriquant systématiquement chaque cas restant dans le else du précédent:
note = 5.25
# Version imbriquee: chaque else contient un nouveau if
if note < 4:
mention = "insuffisant"
else:
if note < 5:
mention = "suffisant"
else:
if note < 5.5:
mention = "bien"
else:
mention = "excellent"
print(mention)
bien
Puis à plat, avec des elif:
note = 5.25
# Version a plat: les memes tests, ecrits avec elif
if note < 4:
mention = "insuffisant"
elif note < 5:
mention = "suffisant"
elif note < 5.5:
mention = "bien"
else:
mention = "excellent"
print(mention)
bien
Les deux programmes sont équivalents: nous les avons exécutés sur les 29 valeurs de 0 à 7 par quarts de point, et ils donnent la même mention dans tous les cas. Ce n'est pas une coïncidence, c'est la définition même de elif: elif C: est une abréviation pour «else: suivi d'un bloc contenant if C:». Le langage vous offre cette abréviation précisément parce que la forme imbriquée est illisible dès trois cas.
Comparez-les: la version imbriquée compte quatre niveaux d'indentation, la dernière mention est enfoncée de douze espaces, et il faut compter les else pour savoir à quel if chacun se rattache. La version à plat aligne les quatre cas sur la même colonne; on lit les seuils 4, 5, 5.5 en descendant, on voit immédiatement qu'ils sont croissants et qu'aucun n'a été oublié. Sur dix cas, la première version est simplement impossible à relire.
Combien de niveaux d'indentation compte au maximum la version imbriquée ci-dessus, en comptant la colonne de gauche comme le niveau 0? Autrement dit, de combien d'espaces la ligne mention = 'excellent' est-elle décalée, divisé par quatre?
Les opérateurs logiques
and, or, not
Les conditions réelles combinent plusieurs critères: un retrait est autorisé s'il est positif et inférieur au solde; une réduction s'applique aux moins de 16 ans ou aux détenteurs d'un abonnement; un dossier est incomplet si le formulaire n'est pas signé. Python écrit ces trois combinaisons avec trois mots-clés en anglais, et non avec les symboles &&, ||, ! d'autres langages.
La table de vérité d'un opérateur énumère son résultat pour toutes les combinaisons possibles de ses opérandes. Comme il n'y a que deux valeurs, la table de and et celle de or comptent quatre lignes, et celle de not deux. Le programme suivant les produit.
print(True and True)
print(True and False)
print(False and True)
print(False and False)
print(True or False)
print(False or False)
print(not True)
print(not False
True
False
False
False
True
False
False
True
Le «ou» de la langue courante est souvent exclusif — «fromage ou dessert» n'invite pas à prendre les deux — alors que le or de Python est inclusif. Quand vous avez besoin du «ou exclusif», il faut l'écrire: (a or b) and not (a and b), ou plus simplement a != b lorsque a et b sont des booléens.
Voici ces opérateurs au travail sur le tarif du billet:
age = 20
demi_tarif = True
if age < 16 or demi_tarif:
print("Reduction accordee.")
if age >= 16 and not demi_tarif:
print("Plein tarif.")
Reduction accordee.
Remarquez que demi_tarif est utilisé directement comme condition, sans écrire demi_tarif == True. C'est la forme correcte: la variable est déjà un booléen, la comparer à True est une redondance qui n'apporte rien et alourdit la lecture. On écrit if demi_tarif: et if not demi_tarif:.
La priorité des opérateurs
Comme * lie plus fort que + en arithmétique, les opérateurs logiques ont une priorité. Du plus fort au plus faible: les comparaisons d'abord, puis not, puis and, puis or.
print(True or True and False)
print((True or True) and False)
print(not True and False)
print(not (True and False))
True
False
False
True
La première ligne se lit True or (True and False), soit True or False, soit True. La deuxième, parenthésée autrement, donne True and False, soit False. Deux expressions écrites avec les mêmes symboles dans le même ordre, deux résultats opposés: voilà pourquoi la priorité n'est pas un détail. De même, la troisième ligne se lit (not True) and False et non not (True and False), comme le montre la quatrième.
Que la comparaison lie plus fort que tout le reste est en revanche une bonne nouvelle: note >= 4 and presence >= 0.8 signifie bien (note >= 4) and (presence >= 0.8), et personne n'a besoin d'y penser.
Les lois de De Morgan
Comment nier une condition composée? La réponse mécanique — mettre un not devant et des parenthèses autour — est correcte mais souvent illisible. Les lois de De Morgan, établies par le mathématicien britannique Augustus De Morgan au XIXᵉ siècle, disent comment faire entrer la négation à l'intérieur:
En Python et en français de programmeur: la négation d'un and est le or des négations, et la négation d'un or est le and des négations. En niant, on échange les deux opérateurs. Vérifions-le sur les quatre combinaisons possibles, ce qui constitue une démonstration complète puisqu'il n'y en a pas d'autres:
# Loi de De Morgan: not (a and b) vaut (not a) or (not b)
print(not (True and True), (not True) or (not True))
print(not (True and False), (not True) or (not False))
print(not (False and True), (not False) or (
False False
True True
True True
True True
False False
False False
False False
True True
Les deux colonnes coïncident sur les huit lignes: les deux lois sont vérifiées exhaustivement. Retenez surtout l'erreur que ces lois interdisent, et qui est extrêmement fréquente: la négation de «a et b» n'est pas «non-a et non-b». Nier «il fait froid et il pleut», ce n'est pas affirmer «il ne fait pas froid et il ne pleut pas», c'est affirmer «il ne fait pas froid ou il ne pleut pas» — il suffit qu'un des deux soit démenti.
Un billet est gratuit si le voyageur a moins de 6 ans et voyage accompagné. Laquelle de ces conditions décrit exactement le cas où le billet n'est PAS gratuit?
L'évaluation paresseuse
and et or s'arrêtent dès qu'ils savent
Considérez a and b. Si a est faux, le résultat est False quelle que soit la valeur de b: il est inutile de calculer b. Python en tire les conséquences et n'évalue pas le second opérande. Symétriquement, dans a or b, si a est vrai, le résultat est True sans qu'on ait besoin de b.
Ce n'est pas une optimisation de détail, c'est une garantie du langage, sur laquelle on a le droit de compter pour écrire des conditions qui, sans elle, planteraient. L'exemple canonique est la protection d'une division:
x = 0
if x != 0 and 10 / x > 2:
print("Le quotient depasse 2.")
else:
print("Pas de quotient a comparer.")
Pas de quotient a comparer.
Avec x = 0, le premier opérande x != 0 vaut False. Python conclut immédiatement que le and vaut False, n'évalue jamais 10 / x, et la division par zéro n'a pas lieu. Le programme s'exécute sans incident.
Maintenant, inversons les deux opérandes, ce qui ne change rien à la logique mathématique de la condition:
x = 0
if 10 / x > 2 and x != 0:
print("Le quotient depasse 2.")
else:
print("Pas de quotient a comparer.")
Traceback (most recent call last):
File "quotient.py", line 2, in <module>
if 10 / x > 2 and x != 0:
~~~^~~
ZeroDivisionError: division by zero
Le programme s'arrête. Cette fois, le premier opérande évalué est 10 / x, la division est bel et bien effectuée, et elle échoue. Le garde-fou x != 0 était présent dans la condition, mais placé après: il n'a jamais eu l'occasion de protéger quoi que ce soit.
La leçon est une règle d'écriture: placez toujours le test de possibilité avant le test qui en dépend. On vérifie que le dénominateur n'est pas nul avant de diviser, que la chaîne n'est pas vide avant de regarder son premier caractère (chapitre 5), que l'indice est dans les bornes avant d'accéder à l'élément (chapitre 6). L'ordre des opérandes d'un and n'est pas indifférent, alors que l'ordre des facteurs d'un produit l'est: c'est une des rares asymétries entre l'algèbre et le code, et il faut l'avoir vue une fois.
Les comparaisons enchaînées
Tester l'appartenance à un intervalle est si courant que Python offre pour cela une syntaxe que presque aucun autre langage n'a.
x = 7
print(0 <= x <= 10)
print(0 <= x and x <= 10)
note = 4.5
print(1 <= note <= 6)
print(1 <= 7.0 <= 6)
True
True
True
False
0 <= x <= 10 s'écrit et se lit comme l'encadrement mathématique , et signifie exactement 0 <= x and x <= 10. On peut enchaîner autant de comparaisons qu'on veut, avec n'importe quels opérateurs: a < b < c, a <= b != c, 1 <= mois <= 12.
Vrai, faux, et les valeurs qui font semblant
Python accepte de placer n'importe quelle valeur là où il attend une condition, et pas seulement un booléen. Il convertit alors cette valeur en vrai ou faux selon une règle simple.
print(bool(0))
print(bool(0.0))
print(bool(""))
print(bool(None))
print(bool(3))
print(bool(-1))
print(bool("0"))
print(
False
False
False
False
True
True
True
True
Les deux dernières lignes de la sortie sont celles qui piègent: "0" et "False" sont des chaînes de caractères non vides, donc vraies. La chaîne "False" contient cinq lettres; ce n'est pas le booléen False, et rien dans le langage ne les rapproche. Ce détail explique un bug classique: une réponse lue au clavier avec input() est toujours une chaîne, et une chaîne non vide est toujours vraie — tester if reponse: vérifie seulement que l'utilisateur a tapé quelque chose, jamais qu'il a tapé «oui».
reponse = ""
if reponse:
print("Vous avez repondu:", reponse)
else:
print("Reponse vide.")
nombre = 0
if nombre != 0:
print("Nombre non nul")
else:
print("Le nombre vaut zero")
Reponse vide.
Le nombre vaut zero
Le programme accepte n'importe quelle réponse non vide, non compris. La saisie vaut non: corrigez la condition pour qu'il affiche Refuse.
Comparer des nombres à virgule
Le chapitre 1 a montré le résultat le plus dérangeant du cours: les flottants ne représentent pas exactement les nombres décimaux. Il est temps d'en tirer la conséquence pratique, car c'est dans les conditions qu'elle mord.
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(abs((0.1 + 0.2) - 0.3))
print(abs((0.1 + 0.2) - 0.3) < 1e-9)
0.30000000000000004
False
5.551115123125783e-17
True
0.1 + 0.2 == 0.3 vaut False. Ce n'est pas un bug de Python: c'est le comportement de l'arithmétique binaire en virgule flottante, celle que toutes les machines et tous les langages utilisent. Un dixième n'a pas d'écriture finie en base deux, pas plus qu'un tiers n'en a en base dix; la machine stocke la valeur représentable la plus proche, et l'écart, invisible à l'affichage habituel, survit à l'addition.
La troisième ligne mesure cet écart: , soit cinquante-cinq millionièmes de milliardième de milliardième. C'est négligeable dans toute application concevable — et pourtant == le refuse, parce que == compare des valeurs exactes. La solution est de ne pas demander l'égalité exacte, mais la proximité.
Le seuil n'a rien de sacré: il est simplement beaucoup plus grand que l'erreur d'arrondi accumulée par quelques opérations sur des nombres d'ordre 1 (de l'ordre de ), et beaucoup plus petit que toute différence qui aurait un sens physique dans nos exemples. Sur des nombres très grands ou très petits, il faudrait raisonner en écart relatif; à notre échelle, l'écart absolu suffit.
Voici le cas qui vous concerne directement, avec une moyenne pondérée comme en calcule tout enseignant:
# Moyenne ponderee: 30 % pour le test, 70 % pour l'examen
test = 5.5
examen = 4.0
moyenne = 0.3 * test + 0.7 * examen
print("Moyenne:", moyenne)
print("moyenne == 4.45 :", moyenne == 4.45)
print("tolerance :", abs(moyenne - 4.45) < 1e-9)
Moyenne: 4.449999999999999
moyenne == 4.45 : False
tolerance : True
Mathématiquement, . La machine obtient 4.449999999999999, et un programme qui testerait if moyenne == 4.45: pour accorder une mention la refuserait. Pire: le même programme donnerait la mention à un autre étudiant dont les notes, différentes, tomberaient exactement sur la bonne valeur binaire. Un critère qui dépend de l'arrondi de la machine n'est pas un critère.
Ce programme devrait dire que la facture est soldée, et il dit le contraire. Réparez la comparaison pour qu'il affiche Soldee.
Deux programmes complets
Classer une note sur l'échelle suisse
Assemblons tout ce qui précède. Les notes suisses vont de 1 à 6, la suffisance est à 4, et les demis et quarts de point sont usuels. Le programme reçoit une note, vérifie qu'elle est valide, et lui attribue une mention. Les seuils retenus ici sont une convention de ce cours et non une règle officielle: chaque haute école a la sienne. Ce sont les mêmes dans tout le cours, et le chapitre 4 reprendra cette chaîne telle quelle pour en faire une fonction mention.
# Classement d'une note suisse (echelle 1 a 6, suffisant des 4)
note = 4.5
if note < 1 or note > 6:
mention = "note invalide"
elif note < 4:
mention = "insuffisant"
elif note < 5:
mention = "suffisant"
elif note < 5.5:
mention = "bien"
else
Note: 4.5
Mention: suffisant
Quatre choix méritent d'être justifiés, car ce sont eux qui font la différence entre un programme qui marche et un programme sur lequel on peut compter.
La validité vient en premier. Le test note < 1 or note > 6 est placé avant tout le reste. C'est une garde: aucune des branches suivantes n'a de sens sur une note de 12 ou de . Le mettre à la fin, dans le else, serait impossible, puisque le else est déjà pris par «excellent».
Les seuils sont croissants. En descendant la chaîne, on lit 1, 6, puis 4, 5, 5.5. Chaque elif n'a besoin que de sa borne supérieure, la borne inférieure étant garantie par l'échec des tests précédents. C'est le bénéfice du elif évoqué plus haut, et il rend la relecture presque mécanique.
Les bornes sont strictes d'un seul côté. note < 5 signifie que 5,0 exactement n'est pas «suffisant» mais «bien». C'est un choix, il doit être conscient, et c'est là qu'il faut tester: pour chaque seuil , essayez lui-même, et non seulement et .
Le else final garantit l'exhaustivité. Aucune note valide ne peut sortir de la structure sans mention, donc mention existe forcément à la ligne du print. Si nous avions écrit elif note <= 6: au lieu de else:, une note de 6,0 aurait bien reçu sa mention, mais le programme aurait pu, sur une valeur oubliée, atteindre le print avec une variable mention inexistante — et échouer sur un NameError en production plutôt qu'à l'écriture.
Voici ce que le programme affiche pour une série de notes, obtenu en changeant la deuxième ligne et en le relançant à chaque fois:
note | Mention affichée |
|---|---|
| 0.5 | note invalide |
| 1.0 | insuffisant |
| 3.75 | insuffisant |
| 4.0 | suffisant |
| 4.5 | suffisant |
| 5.0 | bien |
| 5.25 | bien |
| 5.5 | excellent |
| 6.0 | excellent |
| 6.5 | note invalide |
Cette table est le vrai test du programme: dix valeurs, dont les deux bornes invalides, les trois seuils exacts (4,0, 5,0 et 5,5), les deux extrémités valides et trois valeurs intermédiaires. Un programme conditionnel se teste sur ses bords, pas au milieu de ses intervalles.
Le même programme peut évidemment lire la note au clavier. Voici une exécution réelle dans le terminal, avec la ligne que nous avons tapée:
note_texte = input("Votre note: ")
note = float(note_texte)
if note >= 4:
print("Suffisant")
else:
print("Insuffisant")
Votre note: 3.5
Insuffisant
Rappelez-vous le chapitre 1: input() renvoie toujours une chaîne, et "3.5" >= 4 n'aurait aucun sens — Python refuserait avec un TypeError. La conversion par float() est indispensable, et elle échoue bruyamment si l'utilisateur tape autre chose qu'un nombre. Nous apprendrons au chapitre 8 à rattraper cette erreur au lieu de la subir.
Résoudre une équation du second degré
Le second programme complet est le plus classique des exercices de branchement, et il réunit la garde de validité, la chaîne de tests et la comparaison à tolérance. Il résout dans les réels.
Une seule chose nous manque: la racine carrée. Python en a une, math.sqrt, mais elle vit dans un module qu'il faut importer, et l'importation est le sujet du chapitre 8. Nous nous en passons avec l'opérateur ** du chapitre 1, puisque : d ** 0.5 est la racine carrée de d, au même titre que d ** 2 en est le carré.
# Resolution de a*x**2 + b*x + c = 0 dans les reels
a = 1.0
b = -3.0
c = 2.0
if abs(a) < 1e-12:
print("Ce n'est pas une equation du second degre.")
else:
discriminant = b * b - 4 * a * c
print("Discriminant:", discriminant)
if abs(discriminant) <
Discriminant: 1.0
Deux solutions: 1.0 et 2.0
Le discriminant vaut , et les racines , soit 1 et 2. En changeant les trois coefficients, on parcourt les quatre issues:
a | b | c | Ce que le programme affiche |
|---|---|---|---|
| 1.0 | −3.0 | 2.0 | Discriminant: 1.0 — Deux solutions: 1.0 et 2.0 |
| 2.0 | 5.0 | −3.0 | Discriminant: 49.0 — Deux solutions: −3.0 et 0.5 |
| 1.0 | −2.0 | 1.0 | Discriminant: 0.0 — Une solution double: 1.0 |
| 1.0 | 0.0 | 1.0 | Discriminant: −4.0 — Aucune solution reelle. |
| 0.0 | 2.0 | −4.0 | Ce n'est pas une equation du second degre. |
Trois décisions de conception sont à relever, et elles se reverront dans tous vos programmes.
L'imbrication est ici justifiée. Le if abs(a) < 1e-12: extérieur pose une question — est-ce seulement une équation du second degré? — et la chaîne intérieure en pose une autre — combien de racines? On ne peut pas les mettre à plat avec des elif, car le discriminant n'existe pas tant que la première question n'a pas reçu sa réponse. C'est exactement le critère de la section sur l'imbrication.
Le test du cas double vient avant celui du cas positif. Comme l'exemple 2.3 l'a montré, un discriminant théoriquement nul sort du calcul avec un signe arbitraire. En testant d'abord abs(discriminant) < 1e-9, on capture ce cas quel que soit son signe, et les deux branches suivantes ne voient plus que des discriminants franchement non nuls.
La tolérance sur a est plus serrée que celle sur le discriminant ( contre ): a est une donnée, pas le résultat d'un calcul, et un coefficient légitime peut être très petit. Choisir une tolérance, c'est toujours choisir ce qu'on accepte de confondre, et cela se décide grandeur par grandeur.
La table de décision
Énumérer les cas avant d'écrire le code
Les deux programmes précédents ont un point commun: leurs cas ont été listés avant que la première ligne soit écrite. C'est une méthode, et elle a un nom.
Prenons une tarification ferroviaire fictive, inventée pour ce cours: plein tarif CHF 24.00; les moins de 16 ans paient la moitié; les 16 à 25 ans révolus bénéficient de 25 % de réduction; un abonnement demi-tarif donne droit à la moitié du plein tarif; les réductions ne se cumulent pas, on applique la plus avantageuse. Deux critères: la tranche d'âge (trois valeurs) et la possession de l'abonnement (deux valeurs), soit lignes.
| Tranche d'âge | Demi-tarif | Prix dû | Règle appliquée |
|---|---|---|---|
| moins de 16 ans | non | 12.00 | enfant |
| moins de 16 ans | oui | 12.00 | enfant (l'abonnement n'ajoute rien) |
| 16 à 25 ans | non | 18.00 | jeune, −25 % |
| 16 à 25 ans | oui | 12.00 | demi-tarif, plus avantageux |
| 26 ans et plus | non | 24.00 | plein tarif |
| 26 ans et plus | oui | 12.00 | demi-tarif |
La table est complète: tout voyageur a un âge, qui tombe dans une des trois tranches, et possède ou non l'abonnement. Elle est sans recouvrement: les tranches sont disjointes et les deux valeurs de l'abonnement s'excluent. Elle se traduit alors presque mécaniquement, et l'inspection de la colonne «prix dû» suggère même la simplification: quatre des six lignes donnent 12.00.
Les deux questions à se poser
Avant de coder une structure conditionnelle, posez-vous systématiquement ces deux questions, qui sont les deux faces d'un même contrôle.
Mes branches sont-elles exhaustives? Autrement dit, existe-t-il une donnée admissible pour laquelle aucune branche ne s'exécute? Un else final garantit l'exhaustivité par construction, et c'est la raison de la règle: terminez toute chaîne d'elif par un else, même si vous croyez avoir tout prévu — quitte à ce que ce else affiche «cas imprévu», ce qui vaut infiniment mieux qu'un silence.
Mes branches sont-elles exclusives? Autrement dit, deux conditions peuvent-elles être vraies en même temps? Dans une chaîne elif, l'exclusivité est automatique — la première gagne — mais elle peut cacher une intention fausse: si deux lignes de votre table s'appliquent au même cas avec deux prix différents, le programme en choisira un sans vous avertir, et ce sera celui que l'ordre des lignes aura désigné. L'exclusivité doit être vraie dans la table, pas seulement dans le code.
Un dernier signal d'alarme, facile à repérer: une suite de if indépendants là où il fallait une chaîne elif. Quatre if de suite, ce sont quatre tests tous évalués et potentiellement quatre branches exécutées; une chaîne elif, c'est une seule branche. Quand les cas s'excluent, écrivez elif.
Une table de décision comporte trois critères: l'âge (3 tranches), la possession d'un abonnement (oui ou non) et le fait de voyager un jour férié (oui ou non). Combien de lignes compte la table complète?
Synthèse
- Une condition est une expression ordinaire dont la valeur est du type
bool, c'est-à-direTrueouFalse. Les six opérateurs de comparaison==,!=,<,<=,>,>=en produisent;=affecte et ne compare jamais, et l'écrire dans unifdéclenche uneSyntaxErrorexplicite. ifexécute un bloc quand sa condition est vraie,elseprend tout le reste, etelifajoute des cas intermédiaires dont la condition est implicitement complétée par la négation de toutes les précédentes: l'ordre des tests est significatif et une branche inatteignable ne produit aucune erreur.- L'indentation est de la syntaxe: elle seule dit ce qui appartient au bloc. Un bloc manquant donne
IndentationError: expected an indented block, une indentation superflueIndentationError: unexpected indent, et un deux-points oubliéSyntaxError: expected ':'. Quatre espaces, jamais de tabulations. - Un
ifimbriqué devient unelifquand il est seul dans leelseet répond à la même question; il reste imbriqué quand il pose une question qui n'a de sens qu'après la première, comme la validité avant le classement. and,oretnotse combinent selon les priorités comparaison, puisnot, puisand, puisor— parenthésez quand même. Les lois de De Morgan échangentandetoren niant:not (a and b)vaut(not a) or (not b).andetorsont paresseux: le second opérande n'est évalué que si le premier ne suffit pas à conclure, ce qui rendx != 0 and 10 / x > 2sûr et10 / x > 2 and x != 0fatal. Placez toujours la garde avant le calcul qu'elle protège.- Python autorise
0 <= x <= 10, ce que la plupart des langages interprètent autrement; toute valeur non nulle et non vide est «vraie», mais comparez explicitement tant que vous apprenez; et comparez deux flottants calculés avecabs(a - b) < 1e-9, jamais avec==.
Que vaut l'expression not (3 == 3.0) or 2 != 2 en Python?
Exercices
Vous pouvez afficher le corrigé directement sous chaque énoncé après avoir cherché la solution.
Écrivez un programme qui, pour une variable nombre fixée en tête de fichier, affiche si le nombre est positif, négatif ou nul, puis affiche sa valeur absolue — sans utiliser la fonction abs(). Testez-le avec -7.5, 0 et 3.25.
Solution
nombre = -7.5
if nombre > 0:
signe = "positif"
elif nombre < 0:
signe = "negatif"
else:
signe
Une année est bissextile si elle est divisible par 4, sauf si elle est divisible par 100, sauf encore si elle est divisible par 400. Ainsi 2024 est bissextile, 1900 ne l'est pas, 2000 l'est. Écrivez le programme correspondant avec une seule condition composée, en utilisant l'opérateur % du chapitre 1.
Solution
annee = 1900
if (annee % 4 == 0 and annee % 100 != 0) or annee % 400 == 0:
print(annee, "est bissextile.")
Écrivez un programme qui, pour des variables annee et mois données, affiche le nombre de jours du mois. Le mois est un entier de 1 à 12; tout autre valeur doit être signalée comme invalide. Février compte 29 jours les années bissextiles et 28 sinon. Réutilisez la condition de l'exercice 2.2.
Solution
annee = 2024
mois = 2
if mois < 1 or mois > 12:
jours = 0
elif mois == 2:
if (annee %
Trois longueurs a, b et c sont données. Écrivez un programme qui vérifie d'abord qu'elles sont strictement positives, puis qu'elles peuvent former un triangle (chaque longueur doit être strictement inférieure à la somme des deux autres), puis affiche si le triangle est équilatéral, isocèle ou quelconque.
Solution
a = 5.0
b = 5.0
c = 8.0
if a <= 0 or b <= 0 or c <= 0:
print("Les longueurs doivent etre strictement positives.")
Trois variables a, b et c contiennent trois nombres. Écrivez un programme qui les réorganise de sorte que, à la fin, a contienne le plus petit, b celui du milieu et c le plus grand — sans utiliser de liste ni de fonction de tri, que nous ne connaissons pas encore. Affichez les valeurs avant et après.
Solution
L'idée est de procéder par échanges successifs. Un échange de deux variables passe par une troisième, temporaire: si l'on écrivait a = b puis b = a, la valeur initiale de a serait perdue dès la première ligne.
a = 7.0
b = 2.0
c = 5.0
print("Avant:", a, b, c)
if a > b: # on met la plus petite des deux dans a
temporaire =
Références
- A. Downey, Think Python: How to Think Like a Computer Scientist, 3ᵉ édition, O'Reilly, 2024 — chapitre 5, «Conditionals and recursion», pour les opérateurs booléens et l'exécution conditionnelle.
- G. Swinnen, Apprendre à programmer avec Python 3, Eyrolles — chapitre «Sélection: instruction if – else», pour une présentation en français de l'indentation et des blocs.
- E. Matthes, Python Crash Course, 3ᵉ édition, No Starch Press, 2023 — chapitre 5, «If statements», pour les chaînes
if/elif/elseet les tests d'égalité sur les chaînes. - J. Guttag, Introduction to Computation and Programming Using Python, 3ᵉ édition, MIT Press, 2021 — chapitre 2, sur le branchement et les limites de l'arithmétique en virgule flottante.
- La documentation officielle Python, The Python Language Reference, section «Compound statements» (
if) et section «Boolean operations», pour la définition exacte de l'évaluation paresseuse et des comparaisons enchaînées. - La documentation officielle Python, The Python Tutorial, section «More Control Flow Tools», pour la syntaxe et les conventions d'indentation.