1Expressions booleennes et logique
Un programme purement sequentiel execute toujours les memes instructions dans le meme ordre : il calcule, mais ne decide pas. L'instruction conditionnelle introduit le premier mecanisme de controle : choisir une branche selon la valeur de verite d'une expression.
Une expression booleenne est une expression dont l'evaluation produit
True ou False. Elle est construite a partir de comparaisons combinees
par les connecteurs not, and, or.
Operateurs de comparaison
| Operateur | Sens | Exemple vrai |
|---|---|---|
== | egalite des valeurs | 3 == 3.0 |
!= | difference | "a" != "A" |
< <= > >= |
ordre | 2 <= 2 |
in | appartenance | "y" in "Python" |
is | identite des objets | x is None |
=, == et is
=affecte.if x = 3:est uneSyntaxError.==compare les valeurs :[1,2] == [1,2]vautTrue.iscompare les identites (meme objet en memoire) :[1,2] is [1,2]vautFalse, ce sont deux listes distinctes.
On reserve is aux singletons : if x is None:,
if b is True:. Partout ailleurs, on utilise ==.
Les connecteurs logiques
Tables de verite, avec \(P\) et \(Q\) booleens :
| \(P\) | \(Q\) | not P | P and Q |
P or Q |
|---|---|---|---|---|
| V | V | F | V | V |
| V | F | F | F | V |
| F | V | V | F | V |
| F | F | V | F | F |
Le or de Python est le ou inclusif : il est vrai des que l'un des
deux operandes l'est. Le ou exclusif s'ecrit P != Q (ou P ^ Q).
\[ \neg(P \wedge Q) \equiv (\neg P) \vee (\neg Q), \qquad \neg(P \vee Q) \equiv (\neg P) \wedge (\neg Q). \] Elles servent constamment a simplifier une condition negative :
not (a > 0 and b > 0) # equivaut a
a <= 0 or b <= 0 # forme plus lisiblePriorites
Des plus fortes aux plus faibles : arithmetique > comparaisons >
not > and > or. Ainsi
a < b and c > d or e == f
# se lit ((a < b) and (c > d)) or (e == f)
En cas de doute, parentheser : le correcteur ne penalise jamais des parentheses explicites.
Valeur de verite des objets
Toute valeur peut servir de condition. Sont faux : False,
None, 0, 0.0, "", [],
(), {}, set(). Tout le reste est vrai.
t = []
if t: # equivaut a if len(t) > 0
print("liste non vide")
else:
print("liste vide") # c'est cette branche
Ecrire if trouve: et non if trouve == True: ; ecrire
if not t: et non if len(t) == 0:. Ces formes sont celles attendues et
lues instantanement.
2Instructions conditionnelles
Les trois formes
# 1. alternative simple
if condition:
bloc_si_vrai
# 2. alternative complete
if condition:
bloc_si_vrai
else:
bloc_si_faux
# 3. alternative multiple
if condition_1:
bloc_1
elif condition_2:
bloc_2
elif condition_3:
bloc_3
else:
bloc_par_defaut
elif
Les conditions sont evaluees dans l'ordre et la premiere qui est vraie
declenche son bloc ; toutes les suivantes sont ignorees, meme si elles sont vraies.
Exactement une branche est executee (aucune s'il n'y a pas de else et
qu'aucune condition n'est verifiee).
L'ordre des tests compte
def mention(note):
if note >= 10:
return "Passable" # capte TOUT ce qui est >= 10
elif note >= 12:
return "Assez bien" # JAMAIS atteint
elif note >= 14:
return "Bien" # JAMAIS atteint
return "Insuffisant"
Version correcte : tester du cas le plus restrictif au plus large.
def mention(note):
"""Renvoie la mention associee a une note sur 20."""
if note < 0 or note > 20:
return "Note invalide"
if note >= 16:
return "Tres bien"
if note >= 14:
return "Bien"
if note >= 12:
return "Assez bien"
if note >= 10:
return "Passable"
return "Insuffisant"
assert mention(15.5) == "Bien"
assert mention(9.99) == "Insuffisant"
assert mention(20) == "Tres bien"
elif ou return ?
Dans une fonction, chaque branche se terminant par return, les elif
deviennent superflus : la sortie anticipee rend le code plat et lisible. On parle de
clauses de garde. C'est la forme a privilegier.
Conditions imbriquees et aplatissement
# version imbriquee : difficile a relire
def acces(age, abonne, invite):
if age >= 18:
if abonne:
return "OK"
else:
if invite:
return "OK"
else:
return "Refuse"
else:
return "Refuse"
# version aplatie : meme comportement
def acces(age, abonne, invite):
if age < 18:
return "Refuse"
if abonne or invite:
return "OK"
return "Refuse"
L'expression conditionnelle
valeur_si_vrai if condition else valeur_si_faux
signe = 1 if x >= 0 else -1
maxi = a if a > b else b
parite = "pair" if n % 2 == 0 else "impair"
C'est une expression (elle a une valeur), et non une instruction : on peut la
placer dans un calcul, un argument, une comprehension de liste. A reserver aux cas courts ;
au-dela, un vrai if est plus lisible.
:oublie apres la condition →SyntaxError.elsesuivi d'une condition : ecrireelif, paselse if.- Comparer un flottant a une valeur exacte :
if x == 0.1:est presque toujours faux. Ecrireif abs(x - 0.1) < 1e-12:. - Tester
if 0 < x < 10est correct en Python, maisif x == 1 or 2:est un piege :2est toujours vrai, la condition est donc toujours vraie. Ecrireif x == 1 or x == 2:ouif x in (1, 2):.
3Etudes de cas et table de verite interactive
Etude de cas 1 — position d'un point par rapport a un cercle
from math import sqrt
def position(x, y, r):
"""Position du point (x, y) par rapport au cercle de centre O et rayon r > 0.
Renvoie 'interieur', 'sur le cercle' ou 'exterieur'.
"""
d2 = x*x + y*y # on compare les CARRES : pas de racine, pas d'arrondi
r2 = r*r
if d2 < r2:
return "interieur"
if d2 == r2:
return "sur le cercle"
return "exterieur"
assert position(0, 0, 1) == "interieur"
assert position(3, 4, 5) == "sur le cercle"
assert position(2, 2, 1) == "exterieur"
Comparer \(x^2+y^2\) a \(r^2\) plutot que \(\sqrt{x^2+y^2}\) a \(r\) evite un calcul de racine carree (couteux) et une source d'erreur d'arrondi. Sur des entiers, le test devient meme exact. Ce reflexe sert en geometrie algorithmique.
Etude de cas 2 — classification d'un triangle
def triangle(a, b, c):
"""Nature du triangle de cotes a, b, c (reels strictement positifs)."""
if a <= 0 or b <= 0 or c <= 0:
return "cotes invalides"
# inegalite triangulaire : on ordonne pour n'avoir qu'un test
x, y, z = sorted([a, b, c]) # x <= y <= z
if x + y <= z:
return "triangle impossible"
if x == y == z:
return "equilateral"
if abs(x*x + y*y - z*z) < 1e-12:
return "rectangle"
if x == y or y == z:
return "isocele"
return "quelconque"
assert triangle(3, 4, 5) == "rectangle"
assert triangle(2, 2, 2) == "equilateral"
assert triangle(1, 1, 5) == "triangle impossible"
Deux idees reutilisables : trier les donnees pour reduire le nombre de cas (sans tri, l'inegalite triangulaire demanderait trois tests), et valider les entrees en premier pour que le corps de la fonction travaille sur des donnees saines.
Etude de cas 3 — annee bissextile
Une annee est bissextile si elle est divisible par 4, sauf si elle est divisible par 100 sans l'etre par 400. Ainsi 2024 et 2000 sont bissextiles, 1900 ne l'est pas.
def bissextile(a):
"""Teste si l'annee a est bissextile (calendrier gregorien)."""
return a % 4 == 0 and (a % 100 != 0 or a % 400 == 0)
for a, attendu in [(2024, True), (2000, True), (1900, False), (2023, False)]:
assert bissextile(a) == attendu
Noter qu'une fonction booleenne s'ecrit en renvoyant directement l'expression,
jamais sous la forme if cond: return True else: return False.
Saisir une expression booleenne en \(P\) et \(Q\)
(operateurs and, or, not, ==,
!=) et construire sa table ligne par ligne.
L'essentiel du chapitre
==compare des valeurs,isdes objets,=affecte.- Dans une cascade
if / elif / else, la premiere condition vraie gagne : l'ordre des tests fait partie de l'algorithme. - Une fonction booleenne renvoie directement son expression, sans
if ... return True. andetorsont court-circuitants : on place a gauche la condition qui protege la droite.- Les lois de De Morgan permettent d'eliminer un
notenglobant. - Valider les entrees en premier, trier les donnees pour reduire le nombre de cas.