9. Combiner des conditions avec la logique booléenne
Au chapitre 7, nous avons appris les valeurs booléennes et les conditions simples en utilisant des opérateurs de comparaison. Au chapitre 8, nous avons utilisé ces conditions pour prendre des décisions avec des instructions if. Mais les programmes du monde réel ont souvent besoin de vérifier plusieurs conditions à la fois. Faut-il accorder l’accès si l’utilisateur a le bon mot de passe et est connecté ? Faut-il afficher un avertissement si la température est trop chaude ou trop froide ? Faut-il continuer si le fichier n’est pas vide ?
Python fournit trois opérateurs logiques (logical operators) qui nous permettent de combiner et de modifier des valeurs booléennes : and, or et not. Ces opérateurs sont les briques de base pour exprimer une logique de prise de décision complexe dans vos programmes.
9.1) Opérateurs logiques and, or, et not
Les trois opérateurs logiques fonctionnent avec des valeurs booléennes (ou des valeurs qui peuvent être traitées comme des booléens) pour produire de nouveaux résultats booléens.
9.1.1) L’opérateur and
L’opérateur and renvoie True uniquement lorsque les deux opérandes sont vraies. Si l’un des opérandes est faux, l’expression entière est fausse.
# Les deux conditions doivent être vraies
age = 25
has_license = True
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: True
# Si l’une des conditions est fausse, le résultat est False
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: FalseConsidérez and comme un gardien strict : toutes les conditions doivent être remplies pour que la vérification globale réussisse.
Table de vérité pour and :
| Opérande gauche | Opérande droit | Résultat |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
9.1.2) L’opérateur or
L’opérateur or renvoie True lorsque au moins un opérande est vrai. Il ne renvoie False que lorsque les deux opérandes sont faux.
# Au moins une condition doit être vraie
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: True
# Les deux conditions sont fausses
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: FalseConsidérez or comme un gardien indulgent : vous n’avez besoin de satisfaire qu’une condition pour passer.
Table de vérité pour or :
| Opérande gauche | Opérande droit | Résultat |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
Voici un exemple pratique pour un système d’éligibilité à une réduction :
# Le client obtient une réduction s’il est étudiant OU senior
age = 68
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: True
# Un autre client
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: FalseLe premier client est éligible parce qu’il remplit l’un des critères (senior), même s’il n’est pas étudiant.
9.1.3) L’opérateur not
L’opérateur not est un opérateur unaire (unary operator) (il fonctionne sur un seul opérande) qui inverse une valeur booléenne. Il transforme True en False et False en True.
is_raining = False
is_sunny = not is_raining
print(is_sunny) # Output: True
is_raining = True
is_sunny = not is_raining
print(is_sunny) # Output: FalseTable de vérité pour not :
| Opérande | Résultat |
|---|---|
True | False |
False | True |
L’opérateur not est particulièrement utile lorsque vous voulez vérifier l’opposé d’une condition :
# Vérifier qu’un fichier n’est PAS vide
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}") # Output: File has content: False
# Vérifier que l’utilisateur n’est PAS connecté
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}") # Output: Show login prompt: True9.1.4) Combiner plusieurs opérateurs logiques
Vous pouvez combiner plusieurs opérateurs logiques dans une seule expression pour construire des conditions plus sophistiquées :
# Boutique en ligne : livraison gratuite si la commande dépasse 50 $ OU si le client est membre premium
# ET si les articles sont en stock
order_total = 45.00
is_premium = True
in_stock = True
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}") # Output: Free shipping: TrueTraçons cette évaluation :
order_total >= 50s’évalue àFalse(45.00 n’est pas >= 50)is_premiumestTrueFalse or Trues’évalue àTruein_stockestTrueTrue and Trues’évalue àTrue
Voici un autre exemple avec du contrôle d’accès :
# L’utilisateur peut accéder au panneau d’administration s’il est admin
# ET (s’il est sur le réseau interne OU s’il utilise un VPN)
is_admin = True
on_internal_network = False
using_vpn = True
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}") # Output: Can access admin panel: TrueRemarquez les parenthèses autour de (on_internal_network or using_vpn). Elles sont importantes car elles contrôlent l’ordre d’évaluation, tout comme les parenthèses dans les expressions arithmétiques.
9.2) Priorité des opérateurs dans les expressions booléennes (ordre Not, And, Or)
Lorsque vous combinez plusieurs opérateurs logiques sans parenthèses, Python suit des règles de priorité spécifiques pour déterminer l’ordre d’évaluation. Comprendre ces règles vous aide à écrire des conditions correctes et à éviter des bugs subtils.
9.2.1) La hiérarchie de priorité
Python évalue les opérateurs logiques dans cet ordre (de la priorité la plus élevée à la plus faible) :
not(priorité la plus élevée)and(priorité intermédiaire)or(priorité la plus faible)
Cela signifie que not est évalué en premier, puis and, et enfin or.
# Sans parenthèses, la priorité détermine l’ordre
result = True or False and False
print(result) # Output: True
# Comment Python évalue cela :
# Étape 1 : False and False → False (and a une priorité plus élevée que or)
# Étape 2 : True or False → TrueVoyons cela étape par étape avec un exemple plus détaillé :
is_weekend = False
is_holiday = True
has_work = True
# Expression : not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
# Ordre d’évaluation :
# Étape 1 : not has_work → not True → False
# Étape 2 : is_weekend and is_holiday → False and True → False
# Étape 3 : False or False → False
print(f"Has free time: {free_time}") # Output: Has free time: False9.2.2) Utiliser des parenthèses pour plus de clarté
Même si vous comprenez les règles de priorité, l’utilisation de parenthèses rend votre code plus clair et évite les erreurs. Les parenthèses remplacent la priorité par défaut et rendent vos intentions explicites.
# Ambigu sans parenthèses
result = True or False and False
print(result) # Output: True
# Clair avec des parenthèses - que voulait-on vraiment dire ?
result = (True or False) and False
print(result) # Output: False
result = True or (False and False)
print(result) # Output: TrueCes deux expressions produisent des résultats différents ! Les parenthèses changent complètement le sens.
9.2.3) Opérateurs de comparaison et opérateurs logiques ensemble
Les opérateurs de comparaison (comme <, >, ==, !=) ont une priorité plus élevée que les opérateurs logiques. Cela signifie que les comparaisons sont évaluées avant les opérations logiques.
age = 25
income = 50000
# Pas besoin de parenthèses autour des comparaisons
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}") # Output: Eligible for loan: True
# Python l’évalue ainsi :
# Étape 1 : age >= 18 → True
# Étape 2 : income >= 30000 → True
# Étape 3 : True and True → True9.3) Évaluation en court-circuit
Python utilise l’évaluation en court-circuit (short-circuit evaluation) lorsqu’il évalue des expressions booléennes avec and et or. Cela signifie que Python s’arrête dès qu’il connaît le résultat final, en sautant potentiellement l’évaluation des opérandes suivants. Ce comportement est à la fois une optimisation de performance et une technique de programmation utile.
9.3.1) Comment and court-circuite
Avec l’opérateur and, si l’opérande de gauche est False, Python sait que l’expression entière doit être False (car les deux opérandes doivent être vrais pour que and renvoie True). Par conséquent, Python n’évalue pas du tout l’opérande de droite.
# Démonstration simple
x = 5
result = x < 3 and x > 10
print(result) # Output: False
# Évaluation de Python :
# Étape 1 : x < 3 → 5 < 3 → False
# Étape 2 : Comme le côté gauche est False, ne pas évaluer x > 10
# Étape 3 : Renvoyer FalseVoici un exemple pratique montrant pourquoi l’évaluation en court-circuit est importante :
# Vérifier si un nombre est divisible - éviter la division par zéro
numerator = 100
denominator = 0
# Ceci est sûr grâce à l’évaluation en court-circuit
# Si denominator vaut 0, la division n’a jamais lieu
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}") # Output: Is divisible: False
# Sans l’évaluation en court-circuit, cela provoquerait une erreur :
# denominator = 0
# result = numerator % denominator # ZeroDivisionError!L’expression denominator != 0 s’évalue à False, donc Python n’évalue jamais numerator % denominator, ce qui provoquerait une erreur de division par zéro.
Voyons un autre exemple avec des opérations sur les chaînes :
# Vérifier des propriétés de chaîne en toute sécurité
text = ""
# Vérifier que text n’est pas vide ET que le premier caractère est en majuscule
# Sûr car si text est vide, on n’essaie jamais d’accéder à text[0]
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}") # Output: Starts with uppercase: False
# Si on essayait cela sans vérifier la longueur :
# text = ""
# result = text[0].isupper() # IndexError: string index out of range9.3.2) Comment or court-circuite
Avec l’opérateur or, si l’opérande de gauche est True, Python sait que l’expression entière doit être True (car il suffit qu’au moins un opérande soit vrai). Par conséquent, Python n’évalue pas l’opérande de droite.
# Démonstration simple
x = 15
result = x > 10 or x < 5
print(result) # Output: True
# Évaluation de Python :
# Étape 1 : x > 10 → 15 > 10 → True
# Étape 2 : Comme le côté gauche est True, ne pas évaluer x < 5
# Étape 3 : Renvoyer True9.3.3) Applications pratiques de l’évaluation en court-circuit
Éviter des erreurs :
# Accéder à des éléments de liste en toute sécurité
numbers = [1, 2, 3]
index = 5
# Vérifier que index est valide avant d’accéder
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}") # Output: Valid and positive: False
# Sans court-circuit, cela planterait :
# is_valid = numbers[index] > 0 # IndexError!Vérifier plusieurs conditions efficacement :
# Validation de formulaire - s’arrêter à la première erreur
email = "user@example.com"
password = "pass"
age = 25
# Vérifier chaque exigence dans l’ordre le plus susceptible d’échouer
valid_form = (
len(email) > 0 and # Vérification rapide
"@" in email and # Vérification rapide
len(password) >= 8 and # Vérification rapide
age >= 18 # Vérification rapide
)
print(f"Form valid: {valid_form}") # Output: Form valid: False
# S’arrête à la vérification de la longueur du mot de passe, n’évalue pas age9.4) Ce que renvoient les opérateurs and et or avec des opérandes non booléens, et pièges courants des expressions booléennes
Jusqu’ici, nous avons vu and, or et not fonctionner avec des valeurs booléennes. Mais les opérateurs logiques de Python ont un comportement intéressant : ils peuvent fonctionner avec n’importe quelles valeurs, pas seulement True et False. Comprendre ce comportement vous aide à écrire du code plus concis et à éviter des erreurs courantes.
9.4.1) Comprendre les valeurs truthy et falsy (rappel)
Comme nous l’avons appris au chapitre 7, Python traite de nombreuses valeurs non booléennes comme étant soit « truthy » soit « falsy » dans des contextes booléens :
Valeurs falsy (traitées comme False) :
FalseNone0(zéro de n’importe quel type numérique)""(chaîne vide)[](liste vide){}(dictionnaire vide)()(tuple vide)
Valeurs truthy (traitées comme True) :
True- Tout nombre non nul
- Toute chaîne non vide
- Toute collection non vide
# Démontrer le comportement truthy/falsy
if "hello":
print("Non-empty strings are truthy") # Output: Non-empty strings are truthy
if 0:
print("This won't print") # Zero is falsy
else:
print("Zero is falsy") # Output: Zero is falsy
if [1, 2, 3]:
print("Non-empty lists are truthy") # Output: Non-empty lists are truthy9.4.2) Ce que and renvoie réellement
L’opérateur and ne renvoie pas toujours True ou False. À la place, il renvoie l’un de ses opérandes :
- Si l’opérande de gauche est falsy,
andrenvoie l’opérande de gauche (sans évaluer celui de droite) - Si l’opérande de gauche est truthy,
andrenvoie l’opérande de droite
# and renvoie la première valeur falsy, ou la dernière valeur si toutes sont truthy
result = 5 and 10
print(result) # Output: 10
result = 0 and 10
print(result) # Output: 0
result = "hello" and "world"
print(result) # Output: world
result = "" and "world"
print(result) # Output: (empty string)
result = None and "world"
print(result) # Output: NoneTraçons ces exemples :
# Exemple 1 : Les deux sont truthy
result = 5 and 10
# Étape 1 : 5 est truthy, donc évaluer le côté droit
# Étape 2 : Renvoyer la valeur du côté droit : 10
print(result) # Output: 10
# Exemple 2 : Le côté gauche est falsy
result = 0 and 10
# Étape 1 : 0 est falsy, donc le renvoyer immédiatement
# Étape 2 : Ne pas évaluer le côté droit
print(result) # Output: 0
# Exemple 3 : Deux chaînes truthy
result = "hello" and "world"
# Étape 1 : "hello" est truthy, donc évaluer le côté droit
# Étape 2 : Renvoyer la valeur du côté droit : "world"
print(result) # Output: world9.4.3) Ce que or renvoie réellement
De même, l’opérateur or renvoie l’un de ses opérandes :
- Si l’opérande de gauche est truthy,
orrenvoie l’opérande de gauche (sans évaluer celui de droite) - Si l’opérande de gauche est falsy,
orrenvoie l’opérande de droite
# or renvoie la première valeur truthy, ou la dernière valeur si toutes sont falsy
result = 5 or 10
print(result) # Output: 5
result = 0 or 10
print(result) # Output: 10
result = "" or "default"
print(result) # Output: default
result = "hello" or "world"
print(result) # Output: hello
result = None or 0
print(result) # Output: 0Traçons ces exemples :
# Exemple 1 : Le côté gauche est truthy
result = 5 or 10
# Étape 1 : 5 est truthy, donc le renvoyer immédiatement
# Étape 2 : Ne pas évaluer le côté droit
print(result) # Output: 5
# Exemple 2 : Le côté gauche est falsy
result = 0 or 10
# Étape 1 : 0 est falsy, donc évaluer le côté droit
# Étape 2 : Renvoyer la valeur du côté droit : 10
print(result) # Output: 10
# Exemple 3 : Les deux sont falsy
result = None or 0
# Étape 1 : None est falsy, donc évaluer le côté droit
# Étape 2 : Renvoyer la valeur du côté droit : 0 (même si elle est aussi falsy)
print(result) # Output: 09.4.4) Utilisations pratiques de or pour des valeurs par défaut
Un motif courant consiste à utiliser or pour fournir des valeurs par défaut :
# Préférences utilisateur avec valeurs par défaut
user_theme = "" # L’utilisateur n’a pas défini de thème
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: light
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: dark
# Valeurs de configuration
max_retries = None # Non configuré
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 3
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 5Ce motif fonctionne parce que si le côté gauche est falsy (chaîne vide, None, 0, etc.), or renvoie le côté droit (la valeur par défaut).
9.4.12) Résumé de ce que renvoient les opérateurs
Voici un résumé complet de ce que renvoie chaque opérateur logique :
Opérateur and :
- Renvoie le premier opérande falsy
- Si tous les opérandes sont truthy, renvoie le dernier opérande
- Utilise l’évaluation en court-circuit (s’arrête à la première valeur falsy)
Opérateur or :
- Renvoie le premier opérande truthy
- Si tous les opérandes sont falsy, renvoie le dernier opérande
- Utilise l’évaluation en court-circuit (s’arrête à la première valeur truthy)
Opérateur not :
- Renvoie toujours un booléen (
TrueouFalse) notconvertit l’opérande en booléen, puis l’inverse
# Démontrer les trois opérateurs
print(5 and 10) # Output: 10 (both truthy, return last)
print(0 and 10) # Output: 0 (first falsy, return it)
print(5 or 10) # Output: 5 (first truthy, return it)
print(0 or 10) # Output: 10 (first falsy, evaluate second)
print(not 5) # Output: False (5 is truthy, not returns boolean)
print(not 0) # Output: True (0 is falsy, not returns boolean)
print(not "") # Output: True (empty string is falsy)
print(not "hello") # Output: False (non-empty string is truthy)Comprendre ces comportements vous aide à écrire du code plus concis et plus pythonique, mais privilégiez toujours la clarté. Si l’utilisation de ces fonctionnalités rend votre code plus difficile à comprendre, il vaut mieux être explicite.
Dans ce chapitre, nous avons exploré comment combiner des conditions simples en une logique booléenne complexe en utilisant les opérateurs and, or et not de Python. Nous avons appris la priorité des opérateurs, l’évaluation en court-circuit, et le comportement surprenant des opérateurs logiques avec des valeurs non booléennes. Nous avons aussi examiné les pièges courants et les bonnes pratiques pour écrire des expressions booléennes claires et correctes.
Ces outils vous permettent d’exprimer une logique de prise de décision sophistiquée dans vos programmes. Combinés avec les instructions if du chapitre 8, vous pouvez désormais gérer pratiquement toute logique conditionnelle dont vos programmes ont besoin. Dans le prochain chapitre, nous explorerons les expressions conditionnelles, qui offrent une manière compacte de choisir entre deux valeurs en fonction d’une condition.