Outils Conditions deuxième partie

Identification

Infoforall

14 - Opérateurs booléens


Aujourd'hui, vous allez voir comment associer les instructions conditionnelles pour obtenir des opérateurs logiques : agir uniquement si deux conditions sont vraies, agir dès que l'une des conditions au moins est vraie...

Comme ces cas sont très courants, nous verrons qu'ils existent nativement dans Python (et dans tous les langages, ou presque, en réalité).

Evaluation : 5 questions

  questions 06-14-16-18

  question 20

DM 🏠 : Non

Documents de cours PDF : .PDF

Sources latex : .TEX et entete.tex et licence.tex

1 - Opérateur booléen not

Nous allons voir comment décrire certaines conditions logiques de façon plus élégante et concise qu'avec une suite de if-elif-else.

Vous avez déjà vu des opérations qui renvoient un booléen. En voici trois avec leur signature :

L'opérateur d'appartenance

type_quelconque in type_structure -> bool

L'opérateur d'égalité de contenu

un_type_quelconque == un_type_quelconque -> bool

L'opérateur de différence de contenu

un_type_quelconque != un_type_quelconque -> bool

Nous allons maintenant voir les opérateurs booléens : ils renvoient un booléen mais doivent également recevoir des booléens en entrée.

1.1 Instruction conditionnelle : tester un prédicat à True

RAPPEL : un prédicat est une fonction qui répondra True ou False sur toutes les entrées qu'on lui fournit.

Présentation du prédicat-exemple

Imaginons qu'on dispose d'un prédicat contient() qui permet de tester si une chaine de caractères contient un mot précis qu'on recherche, en comparant les deux en minuscules.

1 2 3 4 5
def contient(chaine:str, mot:str) -> bool: """Prédicat qui renvoie True si le mot est dans le string""" chaine_minus = chaine.lower() # La chaine mais en miniscules mot_minus = mot.lower() # Le mot mais en miniscules return mot_minus in chaine_minus

Un exemple d'utilisation :

>>> contient("Je veux me plaindre de mes notes bla bla", "PLAINDRE") True
Bonne pratique de programmation

On se souvient qu'on exécute le bloc si l'expression booléenne fournie derrière le if est évaluée à True.

Voici un programme qui permet de récupérer tous les plats qui contiennent des poivrons parmi une liste de plats qu'on aime bien par exemple :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
plats = [ "crêpes au sucre: farine de blé; oeufs; lait; sucre", "pates carbonara : pâtes(blé); fromage; lardon(porc); crême épaisse; basilic", "moules frites : moule; vin blanc; oignons; persil; frites(pommes de terre)", "ratatouille : poivrons verts; poivrons rouges; aubergines; courgettes; tomates; oignons; ail; thym; laurier; persil", "boeuf aux carottes : boeuf; carottes; oignons; vin blanc; ail" "poulet basquaise : tomates; poivrons rouges; poivrons verts; oignons; poulet; thym; laurier; persil" ] def contient(chaine:str, mot:str) -> bool: """Prédicat qui renvoie True si le mot est dans le string""" chaine_minus = chaine.lower() # La chaine mais en miniscules mot_minus = mot.lower() # Le mot mais en miniscules return mot_minus in chaine_minus for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivrons"): # si le plat contient des poivrons print(plat)

On voit donc qu'il suffit de placer le prédicat : s'il est interprété à True, on effectue le bloc. Sinon, non.

Mauvaise pratique de programmation

Nous aurions pu écrire ceci mais c'est plus long et inutile puisque le principe est bien de faire le bloc si l'expression est évaluée à True :

16 17 18
for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivrons") == True: # si le plat contient des poivrons print(plat)

Evitez d'utiliser cette version : elle fonctionne mais montre que vous n'avez pas vraiment compris le principe du if.

⚙ 01° Imaginons qu'on veuille tester si une moyenne est suffisante pour valider un bloc d'enseignement à l'université. Créons pour cela la fonction est_valide().

1 2 3
def est_valide(note:int) -> bool: """Prédicat qui renvoie True si la note est supérieure ou égale à 10""" return note >= 10

Les deux utilisations ci-dessous sont-elles valides ? L'une des deux versions elle-est à privilégier ?

Version A

1 2
if est_valide(15): print(Semestre validé. Passage en deuxième année)

Version B

1 2
if est_valide(15) == True: print(Semestre validé. Passage en deuxième année)

...CORRECTION...

Par défaut, Python évalue la condition et réalise les actions du bloc si la condtion est évaluée à True. Mais comme ici nous obtenons directement un booléen, pas la peine d'en rajouter : le cas A est suffisant et clair.

Ok
if est_valide(15):

Vous auriez pu taper ceci également, mais c'est inutile :

Ok mais bof bof
if est_valide(15) == True:

Voyons maintenant, l'inverse : comment réaliser une action uniquement si on vous répond FAUX.

1.2 Instruction conditionnelle : not pour tester un prédicat à False

Imaginons qu'on veuille récupérer les plats qui ne contiennent pas de poivron. On dispose du prédicat contient() qui permet de savoir si un mot (un ingrédient par exemple) est contenu dans une chaîne de caractères (la recette par exemple).

Très mauvaise pratique de programmation

A éviter absolument !

On veut agir lorsque le prédicat est évalué à False. De façon naïve, on pourrait se dire : si le prédicat est vrai, on ne fait rien, sinon on agit.

16 17 18 19 20
for plat in plats: if contient(plat, "poivron"): pass else: print(plat)
Assez mauvaise pratique de programmation

On se souvient qu'on exécute le bloc si l'expression booléenne fournie derrière le if est évaluée à True.

On pourrait donc tester le fait que l'expression soit évaluée à False !

16 17 18
for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivrons") == False: # si le plat ne contient pas de poivron print(plat)

Evitez d'utiliser cette version : nous allons voir ci-dessous qu'il existe une meilleure façon de faire.

Bonne pratique de programmation : utiliser not

On peut faire mieux en utilisant le mot-clé not : ce mot-clé inverse la valeur du booléen situé derrière lui.

not bool -> bool

>>> b = False >>> b False >>> not b True

On peut alors écrire plus proprement le fait d'agir uniquement si le mot "Poivron" n'apparaît pas :

16 17 18
for plat in plats: # pour chaque plat du tableau plats if not contient(plat, "Poivrons"): # si le plat ne contient pas de poivron print(plat)

Le programme dans sa totalité :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
plats = [ "crêpes au sucre: farine de blé; oeufs; lait; sucre", "pates carbonara : pâtes(blé); fromage; lardon(porc); crême épaisse; basilic", "moules frites : moule; vin blanc; oignons; persil; frites(pommes de terre)", "ratatouille : poivrons verts; poivrons rouges; aubergines; courgettes; tomates; oignons; ail; thym; laurier; persil", "boeuf aux carottes : boeuf; carottes; oignons; vin blanc; ail" "poulet basquaise : tomates; poivrons rouges; poivrons verts; oignons; poulet; thym; laurier; persil" ] def contient(chaine:str, mot:str) -> bool: """Prédicat qui renvoie True si le mot est dans le string""" chaine_minus = chaine.lower() # La chaine mais en miniscules mot_minus = mot.lower() # Le mot mais en miniscules return mot_minus in chaine_minus for plat in plats: # pour chaque plat du tableau plats if not contient(plat, "Poivrons"): # si le plat ne contient pas de poivron print(plat)

⚙ 02° Imaginons qu'on veuille convoquer à un oral de rattrapage les étudiants ayant raté leur semestre. Nous voulons toujours utiliser la fonction est_valide().

1 2 3
def est_valide(note:int) -> bool: """Prédicat qui renvoie True si la note est supérieure ou égale à 10""" return note >= 10

Les deux utilisations ci-dessous sont-elles valides ? L'une des deux versions elle-est à privilégier ?

Version A

1 2
if not est_valide(15): print(Semestre raté. Convocation à l'oral)

Version B

1 2
if est_valide(15) == False: print(Semestre raté. Convocation à l'oral)

...CORRECTION...

La version A est la plus élégante.

Ok et élégant
if not est_valide(15):

Comme la fonction renvoie un booléen, nous pourrions également utiliser la version B mais c'est moins "propre" :

Ok mais bof bof
if est_valide(15) == False:
Ok mais bof bof
if est_valide(15) != True:

⚙ 03° Quelle va être la ligne interprétée (L2 ou L4) si comparer() renvoie False sur cet appel.

1 2 3 4
if not comparer(choix, m): tache_1() else: tache_2()

...CORRECTION...

La ligne 1 va être progressivement évaluée de cette manière :

if not comparer(choix, m)

if not False

if True

Comme la condition de la ligne 1 donne True, on effectue son bloc et on exécute donc la fonction tache_1().

1.3 Table de vérité du not

Voici la table de vérité de l'opérateur NON :

Valeur de a not a
False True
True False

2 - Opérateur booléen and

2.1 Instruction conditionnelle : and pour utiliser plusieurs conditions en même temps

Imaginons qu'on veuille récupérer les plats qui contiennent du poivron et de l'aubergine. On dispose du prédicat contient() qui permet de savoir si un mot (un ingrédient par exemple) est contenu dans une chaîne de caractères (la recette par exemple).

Méthode possible mais problématique dans certains cas

On commence par filtrer sur les poivrons et on rajoute une instruction conditionnelle dans la première.

16 17 18 19
for plat in plats: if contient(plat, "poivron"): if contient(plat, "aubergine"): print(plat)

Si on ne doit rien faire lorsque c'est faux, c'est correct.

Par contre, si on doit agir lorsque ce n'est pas vrai, cela pose problème puisqu'il est possible qu'on soit obligé de faire du copier-coller ou qu'on oublie un cas.

16 17 18 19 20 21 22 23
for plat in plats: if contient(plat, "poivron"): if contient(plat, "aubergine"): print(plat) else: faire_un_truc() else: faire_un_truc()
Bonne pratique de programmation : utiliser and

On peut faire mieux en utilisant le mot-clé and : ce mot-clé est un opérateur booléen qui

  • renvoie Vrai uniquement si les entrées sont toutes vraies
  • renvoie Faux dans tous les autres cas.

bool and bool -> bool

>>> a = True >>> b = True >>> a and b True >>> b = False >>> a and b False

Pour afficher proprement les plats qui contiennent des poivrons et des aubergines :

16 17 18
for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivron") and contient(plat, "Aubergine"): # si le plat contient poivrons ET aubergines print(plat)

Notez bien la présence d'une condition à gauche et à droite du mot-clé and.

De plus, si on veut agir lorsque ce n'est pas vrai, la structure ne nécessite plus de faire des copier-coller :

16 17 18 19 20
for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivron") and contient(plat, "Aubergine"): # si le plat contient poivrons ET aubergines print(plat) else: # sinon, il en manque au moins un des deux faire_un_truc()

Le programme dans sa totalité :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
plats = [ "crêpes au sucre: farine de blé; oeufs; lait; sucre", "pates carbonara : pâtes(blé); fromage; lardon(porc); crême épaisse; basilic", "moules frites : moule; vin blanc; oignons; persil; frites(pommes de terre)", "ratatouille : poivrons verts; poivrons rouges; courgettes; tomates; oignons; ail; thym; laurier; persil", "boeuf aux carottes : boeuf; carottes; oignons; vin blanc; ail" "poulet basquaise : tomates; poivrons rouges; poivrons verts; oignons; poulet; thym; laurier; persil" ] def contient(chaine:str, mot:str) -> bool: """Prédicat qui renvoie True si le mot est dans le string""" chaine_minus = chaine.lower() # La chaine mais en miniscules mot_minus = mot.lower() # Le mot mais en miniscules return mot_minus in chaine_minus for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivron") and contient(plat, "Aubergine"): # si le plat contient poivrons ET aubergines print(plat)
2.2 Table de vérité du and

Voici la table de vérité de l'opérateur ET :

Valeur de a Valeur de b a and b
False False False
False True False
True False False
True True True

A RETENIR : le ET est VRAI uniquement lorsque toutes les entrées sont à VRAI.

Imaginons qu'on veuille créer une fonction est_valide() qui vérifie qu'une note est bien comprise entre 0 et 20.

1 2 3 4 5 6 7
def est_valide(n:int) -> bool: """Prédicat qui renvoie True si n est un entier dans [0;20]""" if n > 20: return False elif n < 0: return False return True

⚙ 04° Expliquer pourquoi la fonction est_valide() ne renvoie pas systématiquement True alors qu'il s'agit de la dernière ligne de cette fonction.

...CORRECTION...

Tout simplement car on sort de la fonction dès qu'on rencontre un return.

Si l'un des deux tests précédents renvoie True, on va donc exécuter le return False et sortir de la fonction.

✎ 05° Un élève propose la fonction ci-dessous. Expliquer clairement pourquoi sa fonction n'est pas correcte alors qu'elle ne déclenche pas d'erreur et qu'elle renvoie bien True pour une note n de 15 par exemple.

1 2 3 4 5 6 7
def est_valide(n:int) -> bool: """Prédicat qui renvoie True si n est un entier dans [0;20]""" if n <= 20: return True elif n >= 0: return True return False

Si vous ne trouvez pas, demandez-vous ce qu'elle renvoie pour une note de -15.

Pour obtenir un code plus lisible et concis, il existe l'opérateur and qui permet d'associer plusieurs conditions.

Revoyons comment cela fonctionne.

⚙ 06° Tapez les instructions ci-dessous dans la console.

>>> note = 15 >>> note >= 0 True >>> note < 0 False >>> note <=20 True >>> note > 20 False >>> note >= 0 and note <= 20 True >>> note = 25 >>> note >= 0 and note <= 20 False

Question : que doivent valoir a et b pour que l'expression booléenne a and b soit évaluée à True ?

...CORRECTION...

L'expression n'est évaluée à True qui si les deux expressions a b sont True. Il s'agit d'un ET logique.

Voici donc une manière plus simple de créer notre fonction est_valide(). Cela ne change en rien son utilisation : pour l'utilisateur, le changement est totalement invisible.

1 2 3
def est_valide(n:int) -> bool: """Fonction qui renvoie True si n est un entier dans [0;20]""" return n >= 0 and n <= 20

Une seule ligne, hors documentation. Pas mal non ?

✎ 07° Un élève propose une autre version de cette fonction. Expliquer clairement pourquoi vous pensez que sa fonction est correcte ou fausse (pensez à regarder les préconditions sur le paramètre n).

1 2 3
def est_valide(n:int) -> bool: """Prédicat qui renvoie True si n est un entier dans [0;20]""" return n > -1 and n < 21
2.3 Instruction conditionnelle : une erreur typique

Voici une erreur courante que tout le monde va faire un jour ou l'autre.

Imaginons qu'on veuille vérifier que deux notes soient bien supérieures à 10 : en français, nous dirions que note1 et note2 sont supérieures à 10.

La faute courante

Voici l'erreur sémantique qu'on peut taper très facilement :

1
return note1 and note2 > 10

Pourquoi est-ce que cela ne fonctionne pas comme vous le voulez ?

  1. D'abord car les opérateurs de comparaison sont prioritaires sur les opérateurs logiques. En réalité, vous avez écrit ceci :
  2. 1
    return note1 and (note2 > 10)
  3. L'opérateur and travaille donc sur les deux propositions suivantes :
    1. note1 : faux si note1 est nulle ou vide, vrai sinon !
    2. note2 > 10 : rien d'étrange pour celle-ci.

La traduction que fait Python de cette ligne est donc : est-ce que note1 contient quelque chose et est-ce que note2 est supérieure à 10.

Cela n'est donc pas ce que nous voulions au départ...

La bonne façon de traduire ceci en Python

Nous n'avons pas le choix. Il faut parler à Python comme il le désire : il faut donc placer une propriété à gauche du and et une propriété à droite du and.

1
return note1 > 10 and note2 > 10

Les opérateurs de comparaison étant prioritaires sur les opérateurs logiques, c'est comme si nous avions écrit ceci :

1
return (note1 > 10) and (note2 > 10)

Cela veut bien dire ce qu'on voulait au départ : est-ce que note1 est supérieure à 10 et est-ce que note2 est supérieure à 10.

Bien entendu, en français, on évitera la répétition de la propriété.

3 - Opérateur booléen or

3.1 Instruction conditionnelle : or pour utiliser plusieurs conditions au choix

Imaginons qu'on veuille récupérer les plats qui contiennent soit du poivron, soit de l'aubergine, voire les deux à la fois. On dispose du prédicat contient() qui permet de savoir si un mot (un ingrédient par exemple) est contenu dans une chaîne de caractères (la recette par exemple).

Méthode possible mais qui nécessite du copier-coller

On commence par filtrer sur les poivrons, sinon, on filtre sur les aubergines.

16 17 18 19 20
for plat in plats: if contient(plat, "poivron"): print(plat) elif contient(plat, "aubergine"): print(plat)

Ici, on a bien deux fois la même instruction sur les lignes 18 et 20.

Bonne pratique de programmation : utiliser or

On peut faire mieux en utilisant le mot-clé or : ce mot-clé est un opérateur booléen qui

  • renvoie Faux uniquement si les entrées sont toutes fausses
  • renvoie Vrai dans tous les autres cas (c'est à dire, lorsque une des entrées au moins est vraie).

bool or bool -> bool

>>> a = True >>> b = True >>> a or b True >>> b = False >>> a or b True

Pour afficher proprement les plats qui contiennent des poivrons OU des aubergines, ou les deux :

16 17 18
for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivron") or contient(plat, "Aubergine"): # si le plat contient poivrons Ou aubergines print(plat)

Notez bien la présence d'une condition à gauche et à droite du mot-clé or.

Le programme dans sa totalité :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
plats = [ "crêpes au sucre: farine de blé; oeufs; lait; sucre", "pates carbonara : pâtes(blé); fromage; lardon(porc); crême épaisse; basilic", "moules frites : moule; vin blanc; oignons; persil; frites(pommes de terre)", "ratatouille : poivrons verts; poivrons rouges; courgettes; tomates; oignons; ail; thym; laurier; persil", "boeuf aux carottes : boeuf; carottes; oignons; vin blanc; ail" "poulet basquaise : tomates; poivrons rouges; poivrons verts; oignons; poulet; thym; laurier; persil" ] def contient(chaine:str, mot:str) -> bool: """Prédicat qui renvoie True si le mot est dans le string""" chaine_minus = chaine.lower() # La chaine mais en miniscules mot_minus = mot.lower() # Le mot mais en miniscules return mot_minus in chaine_minus for plat in plats: # pour chaque plat du tableau plats if contient(plat, "Poivron") or contient(plat, "Aubergine"): # si le plat contient poivrons OU aubergines print(plat)
3.2 Table de vérité du or

Le OU logique (celui de Python)

Voici la table de vérité de l'opérateur OU :

Valeur de a Valeur de b a or b
False False False
False True True
True False True
True True True

A RETENIR : le OU est FAUX uniquement lorsque toutes les entrées sont à FAUX.

Le OU exclusif

Le OU logique n'est pas celui de la vie de tous les jours. Si on vous dit "fromage ou dessert" à la cantine, cela veut dire malheureusement que vous ne pouvez en prendre qu'un.

Ce OU particulier se nomme le OU-EXCLUSIF en informatique, XOR an anglais. Si deux entrées sont vraies, le XOR sera faux.

Voici sa table de vérité :

Valeur de a Valeur de b a xor b
False False False
False True True
True False True
True True False

⚙ 08° Compléter la table de vérité du OU :

Valeur de a Valeur de b a or b
True True ?
False True ?
True False ?
False False ?

Cela revient à chercher cela avec Python :

a or b

...CORRECTION...

Valeur de a Valeur de b a or b
True True True
False True True
True False True
False False False

✎ 09° QCM : On demande à quatre élèves de créer une fonction-prédicat note_invalide(). Elle doit renvoyer True si la note n n'est pas dans [0;20].

On vous fournit ci-dessous la réponse de leur fonction. Quel élève fournit la bonne façon de répondre ?

  • A : return n >= 0 or n <= 20
  • B : return n <= 0 or n >= 20
  • C : return n > 0 or n < 20
  • D : return n < 0 or n > 20

4 - Priorité dans Python

4 Priorité des opérateurs logiques (P.A.C.Bo)

Entre eux
  • D'abord not
  • Ensuite and (équivalent de la multiplication)
  • Enfin or (équivalent de l'addition)
Par rapport aux autres opérateurs : P.A.C.Bo
  • P - Les plus prioritaires : les parenthèses ()
  • A - Ensuite les opérateurs arithmétiques
    • **
    • * / // %
    • + -
  • C - Ensuite les opérateurs de comparaison
    • les opérateurs d'appartenance in et not in
    • les opérateurs de comparaison supérieur et inférieur : >, >=...
    • les opérateurs d'égalité et différence ==, !=
  • BO - Ensuite les opérateurs logiques booléens
    • not
    • and
    • or
  • Les moins prioritaires : if, elif, else
Exemple

Inutile de savoir le refaire par coeur : en cas de doute, mettez des parenthèses. C'est l'habitude qui créera la connaissance de l'ordre P.A.C.Bo

if not a + 8 > b * 2 and a > 0

Op. Arithmétiques : if not (a + 8) > (b * 2) and a > 0

O. de Comparaison : if not ((a + 8) > (b * 2)) and (a > 0)

Op. Booléen (not) : if (not ((a + 8) > (b * 2))) and (a > 0)

Le - prioritaire  : if ((not ((a + 8) > (b * 2))) and (a > 0))

5 - FAQ

Rien pour le moment

Activité publiée le 20 07 2024
Dernière modification : 24 07 2024
Auteur : ows. h.