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 marchebool() 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 exercicebool("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 : Falsebool(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 : 24Baignade !, 32Baignade !, 23.9Trop froid, 32.1Trop 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 ; 1900non (divisible par 100 mais pas 400) ; 2000 → bissextile (divisible par 400).

Pourquoi ça marche1900 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 exerciceyear % 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 exerciceuser == "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 (andor). 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/ouiREFUSE (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.