Solutions — série 02-2 (opérateurs logiques)
Rappel de la règle d'or : on n'ouvre ce fichier qu'après avoir terminé (ou sérieusement séché sur) la série. Compare le raisonnement, pas seulement le code : une solution différente qui marche est souvent valable.
Exercice facile a — prédire avec les priorités
Raisonnement — On applique l'ordre : comparaisons, puis not, puis and, puis or. Ligne 3 : le and se fait AVANT le or. Ligne 4 : le not ne porte que sur True, pas sur toute l'expression.
Solution
print(True and False) # False : and exige les deux
print(not False) # True
print(True or False and False) # True : lu True or (False and False)
print(not True or True) # True : lu (not True) or True → False or True
Pourquoi ça marche — Lignes 3 et 4 sont des pièges de priorité purs : and colle plus fort que or, not colle plus fort que tout. Si tu as répondu False à la ligne 3, tu as lu (True or False) and False — c'est LA confusion que les parenthèses systématiques évitent.
Erreur classique sur cet exercice — Lire de gauche à droite comme du français, sans priorité. Python n'est pas du français : c'est un arbre d'opérations, and en bas, or en haut.
Variante plus difficile — Prédis not False and False puis not (False and False). (Réponses : False puis True — même paire qu'en section 4-a de la leçon, redémontrée.)
Exercice facile b — truthy ou falsy ?
Raisonnement — La règle courte de la leçon : falsy = zéro (0, 0.0), chaîne vide "", False. TOUT le reste est truthy — le contenu de la chaîne n'a aucune importance, seule compte sa non-vacuité.
Solution
print(bool("")) # False : chaîne vide
print(bool("False")) # True : chaîne NON vide (le contenu est ignoré !)
print(bool(0)) # False : zéro
print(bool(0.0)) # False : zéro aussi, en float
print(bool(-5)) # True : non nul (le signe ne compte pas)
print(bool(" ")) # True : une espace est un caractère — non vide
Pourquoi ça marche — bool() ne lit pas, il mesure : un nombre est-il nul, une chaîne est-elle vide ? "False" contient cinq caractères → truthy. -5 n'est pas zéro → truthy.
Erreur classique sur cet exercice — bool("False") → répondre False « parce que c'est écrit dedans ». Python ne lit pas le sens des chaînes. Même piège avec "0" : non vide → True.
Variante plus difficile — Sans exécuter : que vaut bool(bool("")) ? Et bool("bool(0)") ? (Réponses : False — bool(False) reste False ; et True — c'est une chaîne non vide, peu importe qu'elle ressemble à du code.)
Exercice facile c — la baignade et ses deux écritures
Raisonnement — « Entre 24 et 32 inclus » = deux conditions simultanées : t >= 24 ET t <= 32. Le chaînage 24 <= t <= 32 dit la même chose en une expression. Les deux cas restants se départagent avec un elif t < 24: et un else.
Solution
# ex-02-2-c.py
t = float(input("Temperature (degC) ? "))
if 24 <= t <= 32: # ou : t >= 24 and t <= 32 — équivalent
print("Baignade !")
elif t < 24:
print("Trop froid")
else:
print("Trop chaud")
Vérification aux bornes : 24 → Baignade !, 32 → Baignade !, 23.9 → Trop froid, 32.1 → Trop chaud.
Pourquoi ça marche — Python lit 24 <= t <= 32 comme 24 <= t and t <= 32 : les deux écritures sont strictement équivalentes. <= (et non <) rend les bornes incluses, ce que « inclus » exigeait — d'où le test à 24 et 32 pile.
Erreur classique sur cet exercice — Écrire if 24 <= t and <= 32: : SyntaxError — chaque côté d'un and doit être une expression complète (t <= 32, avec son t). Le and ne « factorise » pas la variable ; seul le chaînage le fait.
Variante plus difficile — Inverse la logique : écris d'abord if t < 24 or t > 32: (« PAS baignade ») avec le else pour la baignade. Puis compare avec not (24 <= t <= 32) — c'est la loi de De Morgan de l'exercice difficile qui pointe le bout de son nez.
Exercice moyen a — le menu qui accepte tout
Raisonnement — Python lit choice == "1" or "2" comme (choice == "1") or ("2"). Pour la saisie 9 : "9" == "1" vaut False, donc le or renvoie son deuxième opérande, "2" — chaîne non vide, truthy. Le if est pris : Choix valide, pour n'importe quelle saisie. Le else est du code mort.
Solution
# ex-02-2-d.py — version corrigée
choice = input("Ton choix (1 ou 2) ? ")
if choice == "1" or choice == "2":
print("Choix valide")
else:
print("Choix invalide")
Avec 9 : Choix invalide. Avec 1 ou 2 : Choix valide.
Pourquoi ça marche — Chaque côté du or est désormais une comparaison complète, qui vaut vraiment True ou False selon la saisie. Plus aucun opérande « nu » dont la truthiness parasite la logique.
Erreur classique sur cet exercice — La « correction » if choice == "1" or choice == 2: — le deuxième opérande compare à l'entier 2 : input() renvoyant une str, ce test est toujours False, et le choix 2 devient invalide. Deux leçons en un exercice : opérandes complets ET types homogènes.
Variante plus difficile — Réécris le test avec l'appartenance vue en 02-4 par anticipation : if choice in "12":. Teste avec 1, 2, 9… puis avec la saisie 12. Surprise ? ("12" in "12" est vrai — l'appartenance de CHAÎNES teste les sous-chaînes, pas « l'un des caractères ». L'outil vraiment adapté ⏩ arrive avec les listes, niveau 3.)
Exercice moyen b — l'année bissextile
Raisonnement — La règle se traduit mot à mot : (divisible par 4 ET pas par 100) OU divisible par 400. « Divisible par k » = year % k == 0 (leçon 01-2). Le mélange and/or impose les parenthèses — sans elles, la priorité de and donne le même résultat ici, mais on ne JOUE pas à ça : on écrit ce qu'on pense.
Solution
# ex-02-2-f.py
year = int(input("Annee ? "))
is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
if is_leap:
print(f"{year} est bissextile")
else:
print(f"{year} n'est pas bissextile")
Vérification des quatre cas de contrôle : 2024 → bissextile ; 2023 → non ; 1900 → non (divisible par 100 mais pas 400) ; 2000 → bissextile (divisible par 400).
Pourquoi ça marche — 1900 est le cas qui sépare la vraie règle de la version naïve « divisible par 4 » : 1900 % 4 == 0 mais 1900 % 100 == 0 → la partie gauche du or tombe, et 1900 % 400 != 0 → la droite aussi. Stocker la condition dans is_leap (un bool bien nommé) rend le if lisible comme une phrase.
Erreur classique sur cet exercice — year % 100 != 0 écrit not year % 100 == 0 sans parenthèses : ici ça marche (not s'applique à la comparaison…) — mais es-tu SÛR de la priorité entre not et == ? Écris !=, c'est direct et sans piège. Autre classique : tester seulement 2024 et 2023, jamais 1900 — les quatre cas de contrôle existent parce que chaque morceau de la règle en attrape un.
Variante plus difficile — Réécris la condition SANS and ni or, avec des if imbriqués (comme en 02-1). Compare la lisibilité des deux versions dans ton journal : tu tiens la vraie raison d'être de cette leçon.
Exercice moyen c — la connexion à deux voies
Raisonnement — Deux façons d'entrer : le couple admin/mot de passe correct, OU le compte invité (mot de passe indifférent — on le demande quand même, l'énoncé l'impose). La structure logique : (A and B) or C. Les parenthèses reflètent la politique ; sans elles, la priorité de and donnerait le même découpage, mais on écrit l'intention, pas la chance.
Solution
# ex-02-2-g.py
user = input("Identifiant ? ")
password = input("Mot de passe ? ")
if (user == "admin" and password == "s3cret") or user == "invite":
print("Acces accorde")
else:
print("Acces refuse")
Vérification : admin/s3cret → accordé ; admin/oops → refusé ; invite/whatever → accordé ; bob/s3cret → refusé.
Pourquoi ça marche — Court-circuit à l'œuvre : pour invite, la parenthèse de gauche vaut False dès user == "admin", et le or teste la voie invité. Chaque opérande est une comparaison complète, chaque type est homogène (str contre str).
Erreur classique sur cet exercice — user == "admin" or user == "invite" and password == "s3cret" : la priorité de and accroche le mot de passe au compte INVITÉ — un admin entre alors sans mot de passe. Bug intermittent (seuls certains couples le révèlent), exactement l'erreur n°2 de la leçon. Teste les quatre combinaisons, toujours.
Variante plus difficile — Ajoute une troisième règle : le compte "guest" n'entre QUE si le mot de passe n'est PAS vide (truthiness de la leçon : password tout court). Écris la condition complète en une expression parenthésée.
Exercice difficile a — les lois de De Morgan
Raisonnement — Une équivalence logique se prouve, pour deux variables booléennes, en énumérant les 4 combinaisons — la table de vérité. Pour chaque combinaison, on affiche le verdict : « la version gauche vaut-elle la version droite ? ». Quatre True = équivalence démontrée. Attention au piège de priorité : not X == Y se lit not (X == Y) — il faut parenthéser le membre gauche : (not (a and b)) == (...).
Solution
# ex-02-2-h.py — première loi : not (a and b) == not a or not b
a = True
b = True
print((not (a and b)) == (not a or not b))
a = True
b = False
print((not (a and b)) == (not a or not b))
a = False
b = True
print((not (a and b)) == (not a or not b))
a = False
b = False
print((not (a and b)) == (not a or not b))
Sortie : quatre lignes True. La deuxième loi se vérifie pareil : (not (a or b)) == (not a and not b) → quatre True aussi.
Réécriture demandée : not (age >= 18 and city == "Nice") ⟺ age < 18 or city != "Nice".
Pourquoi ça marche — Le not qui traverse une parenthèse inverse chaque morceau (>= devient <, == devient !=) et BASCULE le connecteur (and ↔ or). Intuition : « il est faux que (majeur ET niçois) » = « il est mineur OU pas niçois ». La table de vérité est la preuve exhaustive : deux booléens n'ont que 4 états possibles, on les a tous testés.
Erreur classique sur cet exercice — Oublier de basculer le connecteur : not (a and b) réécrit not a and not b. Contre-exemple immédiat : a = True, b = False → gauche True, droite False. C'est le bug de logique le plus fourbe des conditions composées, car il passe 3 des 4 cas. Autre erreur : écrire print(not (a and b) == (not a or not b)) sans parenthèses autour du premier membre — Python lit not ((a and b) == ...), qui affiche aussi quatre True ici, mais ne teste PAS ce que tu crois (il teste que gauche et droite diffèrent TOUJOURS… médite là-dessus).
Variante plus difficile — Réécris les 4 blocs répétitifs en DEUX boucles imbriquées quand tu auras fini la leçon 02-4 (for a in (True, False): / for b in (True, False):) — huit lignes deviennent trois. Puis applique De Morgan pour simplifier : not (choice == "1" or choice == "2").
Mini-projet — « contrôle d'accès du club »
Raisonnement — On écrit la politique en français AVANT de coder : « refus absolu sous 12 ans ; sinon, entrée si membre, OU majeur, OU accompagné ». Traduite en logique : age >= 12 and (is_member or age >= 18 or with_adult). Les réponses oui/non sont converties en vrais bool dès la saisie (is_member = answer == "oui") : la condition finale ne manipule que des booléens propres. L'explication de la raison est une chaîne if/elif ORDONNÉE : le refus absolu d'abord (il prime), puis les raisons d'entrée par priorité.
Solution
# door_policy.py
age = int(input("Age ? "))
member_answer = input("Membre du club ? (oui/non) ")
adult_answer = input("Accompagne d'un adulte ? (oui/non) ")
is_member = member_answer == "oui" # une comparaison EST un bool
with_adult = adult_answer == "oui"
allowed = age >= 12 and (is_member or age >= 18 or with_adult)
if allowed:
print("ACCES ACCORDE")
else:
print("ACCES REFUSE")
if age < 12:
print("Raison : moins de 12 ans, refus absolu.")
elif is_member:
print("Raison : membre du club.")
elif age >= 18:
print("Raison : majeur.")
elif with_adult:
print("Raison : accompagne d'un adulte.")
else:
print("Raison : mineur non accompagne et non membre.")
Vérification des cas imposés : 17/non/oui → ACCORDE (accompagné) ; 17/oui/non → ACCORDE (membre) ; 11/oui/oui → REFUSE (moins de 12 ans, membre ou pas) ; 25/non/non → ACCORDE (majeur) ; et 15/non/non → REFUSE (mineur seul, non membre).
Pourquoi ça marche — Le and extérieur fait du critère d'âge minimum un garde-fou : s'il tombe, la parenthèse n'a plus voix au chapitre (court-circuit). À l'intérieur, trois voies d'entrée en or — l'une suffit. La variable allowed sépare la DÉCISION de son AFFICHAGE : la politique tient en une ligne relisible.
Erreur classique sur cet exercice — Traduire « un membre entre dans tous les cas » en mettant is_member HORS du garde-fou : is_member or (age >= 12 and ...) — le membre de 11 ans entre, contredisant le refus absolu. Les deux phrases de l'énoncé se contredisent partiellement : c'est VOULU — une politique réelle a des règles qui se marchent dessus, et c'est l'ordre de priorité (refus absolu d'abord) qui tranche. Si tu as choisi l'autre lecture et su la justifier, ta solution est défendable : l'important est d'avoir VU le conflit.
Variante plus difficile — Ajoute un tarif : gratuit pour les membres, 10 EUR pour les majeurs non membres, 5 EUR pour les mineurs accompagnés. Puis écris la table de tous tes cas de test dans ton journal (âge × membre × accompagné : 12 combinaisons utiles) et coche-les une à une.