Expressions booléennes

Au chapitre précédent nous avons appris à créer des conditions basées sur l’égalité entre deux valeurs. Mais l’égalité n’est pas l’unique opération conditionnelle possible et c’est ce que nous allons voir maintenant.

Opérations booléennes

Vocabulaire

Pour rappel, un test d’égalité peut s’évaluer à True (vrai) ou False (faux). Il s’agit de deux valeurs qui forment un nouveau type de données, le type booléen (bool en Python).
Ce nom provient de George Boole qui a introduit ce concept d’une valeur ne pouvant avoir que deux états, vrai ou faux.

C’est pourquoi on parle généralement d’opération ou d’expression booléenne pour qualifier une expression s’évaluant en un booléen.

Derrière les structures conditionnelles se cache aussi la notion de prédicat. C’est le nom que l’on donne à l’expression booléenne qui conditionne la suite de l’exécution du bloc.
Typiquement, un prédicat peut correspondre à une vérification d’une entrée utilisateur, pour s’assurer qu’un mot de passe est correct ou qu’un nombre se situe bien dans un certain intervalle.

Opérateurs

Outre l’égalité (==), plusieurs opérateurs de comparaison permettent d’obtenir des booléens. On trouve ainsi l’opérateur de différence, !=, qui teste si deux valeurs sont différentes l’une de l’autre.

Cet opérateur s’utilise de la même manière que l’égalité, sur des valeurs de tous types.

>>> 1 != 1
False
>>> 1 != 2
True
>>> 1 != 'abc'
True
>>> 1 != '1'
True

Sont aussi présents les opérateurs d’inégalités < et > pour tester les relations d’ordre :

  • a < b teste si a est strictement inférieur à b ;
  • a > b teste si a est strictement supérieur à b.
>>> 1 < 2
True
>>> 1 > 2
False

Cette fois-ci l’opération n’est possible qu’entre deux valeurs compatibles, c’est-à-dire ordonnables l’une par rapport à l’autre. Ce qui est par exemple le cas des entiers et des flottants.

>>> 5 < 5.1
True
>>> 1.9 > 3
False

Les chaînes de caractères sont ordonnables les unes par rapport aux autres, selon un ordre appelé « ordre lexicographique », une extension au classique ordre alphabétique.
Il est ainsi possible de tester les inégalités entre deux chaînes de caractères.

>>> 'renard' > 'loup'
True
>>> 'loup' < 'lama'
False

L’ordre lexicographique définit explicitement l’ordre de tous les caractères, selon la table unicode. Il faut savoir par exemple que bien que les lettres soient dans l’ordre alphabétique, les majuscules sont considérées comme inférieures aux minuscules, et les lettres accentuées supérieures aux autres.

>>> 'Loup' < 'lama'
True
>>> 'léopard' > 'loup'
True

Il n’est en revanche pas possible de comparer un nombre avec une chaîne de caractères, car aucune relation d’ordre n’existe entre ces deux types.

>>> 5 > '4'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'int' and 'str'

Enfin ces deux opérateurs possèdent des variantes <= et >= correspondant aux opérations « inférieur ou égal » et « supérieur ou égal » en mathématiques.

>>> 1 < 1
False
>>> 1 <= 1
True
>>> 'abc' <= 'abc'
True
Priorités des opérateurs

La priorité entre opérateurs ne concerne pas que les opérations arithmétiques, les opérateurs de comparaison sont eux aussi concernés. Tous ces opérateurs possèdent la même priorité, qui se situe juste en dessous de l’addition. Ainsi, toutes les opérations arithmétiques sont prioritaires sur les opérations de comparaison.

On a donc 3 + 5 == 2 * 4 qui est équivalent à (3 + 5) == (2 * 4).

>>> 3 + 5 == 2 * 4
True
>>> (3 + 5) == (2 * 4)
True

Les opérations booléennes étant des expressions comme les autres, il est tout à fait possible d’en stocker le résultat dans des variables. Il est alors courant d’entourer l’expression de parenthèses pour bien la distinguer de l’opérateur = d’assignation.

>>> equal = (5 == 8)
>>> equal
False
>>> inferior = ('abc' < 'def')
>>> inferior
True

Booléens

Ainsi, le type bool est un type composé de seulement deux valeurs, True et False. Ces valeurs répondent à ce que l’on appelle algèbre de Boole, qui définit les opérations logiques possibles entre les booléens.

Algèbre de Boole

L’opération la plus élémentaire est la négation logique (« NON ») qui se note not en Python. C’est un opérateur unaire (qui ne prend qu’un seul opérande), qui consiste à calculer l’inverse du booléen : True devient False et inversement.

>>> not True
False
>>> not False
True

Il est courant de représenter les opérations booléennes sous forme de tables de vérité. C’est-à-dire de présenter un tableau associant à chaque valeur le résultat de l’opération.

Voici donc la table de vérité de l’opérateur not :

a not a
True False
False True

La négation d’une égalité est alors la même chose que la différence.

>>> not 'abc' == 'def'
True
>>> 'abc' != 'def'
True

Mais il est aussi possible de combiner plusieurs booléens entre eux, de différentes manières.

La première est la conjonction (« ET ») qui permet de tester si deux valeurs sont vraies. Avec a et b deux booléens, l’expression « a ET b » est vraie si et seulement si a est vrai et que b l’est aussi.

En Python, cet opérateur binaire (à deux opérandes) se note and.

>>> True and True
True
>>> True and False
False
>>> 'abc' == 'zzz' and 3 > 0
False

Et sa table de vérité est la suivante.

a b a and b
True True True
True False False
False True False
False False False

Enfin, l’autre opération que l’on trouve en Python sur les booléens est la disjonction (« OU », soit or en Python). L’expression « a OU B » étant vraie si a est vrai ou que b l’est.

>>> True or False
True
>>> False or False
False
>>> 'abc' == 'zzz' or 3 > 0
True

On note que le « OU » est inclusif, ce qui peut se différencier de l’usage courant.
En effet quand on dit « a OU b » on a tendance à imaginer que c’est soit l’un soit l’autre (exclusif) mais pas les deux. En informatique on considère que « a OU b » est vraie aussi si a et b sont vrais.

>>> True or True
True

Voici donc la table de vérité du or.

a b a or b
True True True
True False True
False True True
False False False

Ces opérateurs sont bien sûr composables les uns avec les autres pour former des expressions plus complexes. On utilisera généralement des parenthèses pour isoler les différentes opérations et ne pas avoir à se soucier de leur priorité (voir plus loin).

>>> (True or False) and not (True and False)
True
Conditions et priorités

Ces trois opérateurs peuvent donc s’utiliser au sein d’expressions booléennes pour introduire des blocs conditionnels et ainsi combiner en une seule clause plusieurs sous-conditions.

username = input("Nom d'utilisateur : ")
password = input("Mot de passe : ")

if username == 'admin' and password == 'nimda':
    print('Vous êtes connecté')
else:
    print('Échec de la connexion')

Il est à noter que and, or et not sont les opérateurs en Python ayant la plus faible priorité. Plus faible encore que les opérateurs de comparaison (==, !=, etc.).
C’est pourquoi l’expression dans le code qui précède est équivalente à (username == 'admin') and (password == 'nimda').

Aussi, not est prioritaire sur and qui est lui-même prioritaire sur or, comme on peut le voir dans le code suivant.

>>> not True and False
False
>>> True or True and False
True

Mais ce comportement peut différer d’un langage de programmation à un autre, c’est pourquoi on utilisera toujours des parenthèses autour des sous-expressions booléennes combinant ces différents opérateurs, pour plus de clarté.

>>> (not True) and False
False
>>> True or (True and False)
True
Conversions implicites et explicites

Bien que nous n’ayons pour le moment utilisé de blocs if qu’avec des expressions booléennes, il faut savoir que ceux-ci acceptent n’importe quelle expression, par exemple ici avec un int.

>>> if 5 + 3 * 4:
...     print('Ça marche')
... 
Ça marche

En fait, toute valeur Python est implicitement convertible en booléen, et c’est cette conversion qu’opère Python sur les expressions qu’il rencontre dans un bloc conditionnel.

Ainsi, le nombre zéro (0 ou 0.0) et la chaîne vide ('') s’évaluent à False. Alors que tous les autres nombres (même négatifs) et chaînes de caractères s’évaluent à True

Cette facilité permet de simplifier certaines conditions, comme pour tester si une chaîne entrée n’est pas vide.

name = input('Bonjour, qui est tu ? ')

if name:
    print('Bonjour', name)
else:
    print('Erreur de saisie')

Dans cet exemple, if name: est équivalent à if name != '', car une chaîne vide s’évaluera toujours à False. On préférera donc généralement utiliser cette version raccourcie plutôt qu’ajouter une comparaison inutile.

Il reste bien sûr possible — quand cela est nécessaire — de convertir explicitement une valeur en booléen, en utilisant le type bool comme une fonction sur la valeur que l’on souhaite convertir.
La conversion se fera selon les mêmes règles que celles décrites au-dessus.

>>> bool('hello')
True
>>> bool('')
False
>>> bool(-5.8)
True
>>> bool(0)
False

De la même manière, il n’est pas utile de comparer un booléen à True ou False dans une condition, ce qui ne fait que rallonger l’expression sans y apporter plus de sens. Avec result le résultat d’une opération booléenne (result = (name == 'admin')), on écrira donc simplement if result: ... et jamais if result == True: ....
Et on écrira if not result: ... plutôt que if result == False: ....

Comparaisons chaînées

Les opérateurs de comparaison que l’on a vus peuvent s’enchaîner afin de créer plus facilement des opérations booléennes entre plusieurs valeurs.

Par exemple, si l’on souhaite tester l’égalité entre trois valeurs a, b et c, on pourra écrire a == b == c plutôt que a == b and b == c.

>>> 10 == 10 == 10
True
>>> 10 == 10 == 5
False

Ou encore pour tester une inégalité, 0 < temp < 100 est plus simple à lire que 0 < temp and temp < 100.

>>> 0 < 25 < 100
True
>>> 0 < -25 < 100
False
>>> 0 < 125 < 100
False