🏦 MINES ParisTech, Université PSL
⚖️ CC BY 4.0
L’expression x == y
détermine si les objets
x
et y
sont égaux :
>>> 0 == 0
True
>>> 0 == 1
False
>>> "Hello!" == "Hello!"
True
>>> "Hello" == "World"
False
>>> [1, 2, 3] == [1, 2, 3]
True
>>> [1, 2, 3] == [4, 5, 6]
False
Les tests d’égalité en Python dépendent du type des objets comparés :
il n’y a pas d’interprétation totalement universelle de ==
; il faut se reporter à la documentation des types concernés. Vous
pourrez d’ailleurs décider quel sens donner aux égalités des types que
vous serez amenés à définir.
Le test d’égalité de nombres se passe sans grande surprise si l’on laisse de coté certains propriétés des nombres (à virgule flottante) spéciaux.
Notons simplement que les test d’égalité entre nombres sont suffisamment permissifs pour permettre de comparer des nombres dont le type est différent :
>>> 1 == True
True
>>> 1 == 1.0
True
>>> 1 == 1 + 0j
True
Le nombres à virgule flottante (float
) de Python sont de
précision finie. Par conséquent des erreurs d’arrondi dans les calculs
peuvent faire échouer les tests d’égalité. Ainsi, on a par exemple :
>>> 0.1 + 0.2 == 0.3
False
car l’addition a introduit une (petite) erreur dans le calcul :
>>> 0.1 + 0.2
0.30000000000000004
Le standard IEEE 754 régit la représentation et le calcul des nombres flottants. Il introduit des nombres spéciaux ; il y a ainsi deux zéros distincts (\(0^+\) et \(0^-\)) mais considérés égaux :
>>> +0.0
0.0
>>> -0.0
-0.0
>>> +0.0 == -0.0
True
Plus surprenant, le “non-nombre” nan
(🇺🇸: not a
number) est une valeur spéciale … qui n’est pas égale à
elle-même ! (Tous les “non-nombres” sont réputés différents.)
>>> from math import nan
>>> nan == nan
False
Il faudra utiliser la fonction isnan
pour savoir si une
valeur est un non-nombre.
>>> from math import isnan
>>> isnan(nan)
True
Source: 📖 Standard IEEE 754
Deux collections – listes, n-uplets, dictionnaires, ensembles, etc. – délèguent le test d’égalité aux éléments qui les composent – récursivement si ceux-ci sont également des collections. Ainsi :
>>> [] == [0]
False
>>> [0] == [0]
True
>>> [0] == [1]
False
>>> [0] == [0, 0]
False
>>> [0] == [0.0]
True
>>> [[0]] == [[0.0]]
True
Pour les dictionnaires :
>>> {"a": 1, "b": 2} == {"a": 1, "b": 2}
True
et
>>> {"a": 1, "b": 2} == {"a": 1.0, "b": 2.0}
True
L’ordre des couples clés-valeurs n’a pas d’importance
>>> {"a": 1, "b": 2} == {"b": 2, "a": 1}
True
mais il suffit qu’une clé ou qu’une valeur diffère dans les deux collections comparées pour invalider l’égalité :
>>> {"a": 1, "b": 2} == {"a": 1, "b": 2, "c": 3}
False
>>> {"a": 1, "b": 2} == {"a": 2, "b": 1}
False
La comparaison des chaînes de caractères se passe la plupart du temps comme on s’y attend :
>>> "Hello" == "Hello"
True
>>> "Hello" == "Halo"
False
A ceci près que dans le standard unicode il y a parfois plusieurs façons d’obtenir visuellement le même caractère. Il y a ainsi un caractère “e accent aigu”
>>> "\xe9"
'é'
mais aussi un symbole “accent aigu” qu’on peut combiner à un “e” :
>>> "e\u0301"
'é'
Les deux séquences de code points sont différentes, donc les deux chaînes de caractères sont considérées comme différentes :
>>> "\xe9" == "e\u0301"
False
C’est toutefois beaucoup plus surprenant quand le test est effectué sous la forme suivante :
>>> "é" == "é"
False
L’expression x is y
détermine si l’objet x
est l’objet y
, à la même
identité
x is y
La négation de ==
est !=
, celle de
is
est is not
:
x != y
x is not y
On pourra utiliser le terme est égal à pour affirmer l’égalité entre objets et tout simplement est pour affirmer qu’ils ont la même identité (utiliser le terme “identique” serait trompeur).
L’égalité entre objets est parfois appelée égalité structurelle et l’identité entre objets égalité référentielle.
L’identité x is y
signifie que les variables
x
et y
réfèrent au même objet Python : les
données sont à la même adresse en mémoire. Une copie parfaite d’un objet
aura donc une identité différente de l’originale, alors qu’il sera
considéré égal à l’original. Par contre, si deux objets sont identiques
(au sens de : ont la même identité, sont un seul est unique objet),
alors ils sont nécessairement égaux.
A titre d’exemple, considérons les trois listes a
,
b
et c
:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> c = b
Les listes a
et b
sont égales, ainsi que
b
et c
, mais ne sont pas identiques, elles ne
désignent pas le même objet (en mémoire) ; les variables b
et c
par contre désignent le même objet :
>>> a == b
True
>>> b == c
True
>>> a is b
False
>>> b is c
True
On peut s’assurer que les variables b
et c
désignent le même objet en évaluant l’identifiant de
ces objets (un entier) avec la fonction id
:
>>> id(a)
140636096399680
>>> id(b)
140636098130688
>>> id(c)
140636098130688
>>> id(a) == id(b)
False
>>> id(b) == id(c)
True
Une conséquence importante de cette distinction : les modifications
de la liste (désignée par) b
vont impacter la liste
c
(qui est le même objet), mais pas la liste a
(qui est un objet distinct) :
>>> b.append(4)
>>> b
[1, 2, 3, 4]
>>> c
[1, 2, 3, 4]
>>> a
[1, 2, 3]
x is not y
n’est pas la même chose
que x is (not y)
Bien qu’étant composé de deux mot-clés séparés par un espace,
is not
est un opérateur en tant que tel. L’expression
x is not y
est équivalente à not (x is y)
…
mais plus lisible ! Si l’on a besoin d’utiliser is
et
not
comme des opérateurs distincts, pour signifier
x is (not y)
, il conviendra de garder les parenthèses.
Ainsi, avec
>>> x = 1
>>> y = True
on a
>>> x is not y
True
>>> x is y
False
>>> not (x is y)
True
mais
>>> not y
False
>>> x is (not y)
False
🤔 Comment interpréter
x == y
si les types dex
ety
sont différents ?Si le type de
y
est un sous-type du type dex
, il détermine en priorité quel sens donner à==
; dans le cas contraire c’est le type dex
auquel est donné la priorité.Source: 📖 Méthodes de comparaison riches