Si vous exécutez l'instruction suivante dans Python 3.7, elle imprimera (d'après mes tests) b
:
if None.__eq__("a"):
print("b")
Cependant, None.__eq__("a")
est évaluée à NotImplemented
.
Naturellement, "a".__eq__("a")
est évaluée à True
et "b".__eq__("a")
est évaluée à False
.
J'ai d'abord découvert cela lors du test de la valeur de retour d'une fonction, mais je n'ai rien retourné dans le deuxième cas - la fonction a donc renvoyé None
.
Que se passe t-il ici?
C'est un excellent exemple de la raison pour laquelle les méthodes __dunder__
Ne doivent pas être utilisées directement car elles ne sont souvent pas des remplacements appropriés pour leurs opérateurs équivalents; vous devez utiliser l'opérateur ==
à la place pour les comparaisons d'égalité, ou dans ce cas particulier, lors de la vérification de None
, utilisez is
(passez au bas de la réponse pour plus d'informations ).
Tu as fait
None.__eq__('a')
# NotImplemented
Ce qui renvoie NotImplemented
car les types comparés sont différents. Prenons un autre exemple où deux objets de types différents sont comparés de cette manière, tels que 1
Et 'a'
. Faire (1).__eq__('a')
N'est pas non plus correct et renverra NotImplemented
. La bonne façon de comparer ces deux valeurs d'égalité serait
1 == 'a'
# False
Ce qui se passe ici est
(1).__eq__('a')
Est essayé, ce qui renvoie NotImplemented
. Cela indique que l'opération n'est pas prise en charge, donc'a'.__eq__(1)
est appelée, ce qui renvoie également le même NotImplemented
. Alors,False
est renvoyé.Voici un joli petit MCVE utilisant des classes personnalisées pour illustrer comment cela se produit:
class A:
def __eq__(self, other):
print('A.__eq__')
return NotImplemented
class B:
def __eq__(self, other):
print('B.__eq__')
return NotImplemented
class C:
def __eq__(self, other):
print('C.__eq__')
return True
a = A()
b = B()
c = C()
print(a == b)
# A.__eq__
# B.__eq__
# False
print(a == c)
# A.__eq__
# C.__eq__
# True
print(c == a)
# C.__eq__
# True
Bien sûr, cela n'explique pas pourquoi l'opération renvoie true. C'est parce que NotImplemented
est en fait une valeur véridique:
bool(None.__eq__("a"))
# True
Pareil que,
bool(NotImplemented)
# True
Pour plus d'informations sur les valeurs considérées comme véridiques et fausses, consultez la section des documents sur Test de valeur de vérité , ainsi que cette réponse . Il convient de noter ici que NotImplemented
est véridique, mais cela aurait été une autre histoire si la classe avait défini une méthode __bool__
Ou __len__
Qui renvoyait False
ou 0
respectivement.
Si vous voulez l'équivalent fonctionnel de l'opérateur ==
, Utilisez operator.eq
:
import operator
operator.eq(1, 'a')
# False
Cependant, comme mentionné précédemment, pour ce scénario spécifique , où vous recherchez None
, utilisez is
:
var = 'a'
var is None
# False
var2 = None
var2 is None
# True
L'équivalent fonctionnel de ceci utilise operator.is_
:
operator.is_(var2, None)
# True
None
est un objet spécial, et il n'y a qu'une seule version en mémoire à tout moment. IOW, c'est le seul singleton de la classe NoneType
(mais le même objet peut avoir un nombre quelconque de références). Les directives PEP8 rendent cela explicite:
Les comparaisons avec des singletons comme
None
doivent toujours être faites avecis
ouis not
, Jamais les opérateurs d'égalité.
En résumé, pour les singletons comme None
, une vérification des références avec is
est plus appropriée, bien que ==
Et is
fonctionnent très bien.
Le résultat que vous voyez est causé par le fait que
None.__eq__("a") # evaluates to NotImplemented
correspond à NotImplemented
et la valeur de vérité de NotImplemented
est documentée comme étant True
:
https://docs.python.org/3/library/constants.html
Valeur spéciale qui doit être retournée par les méthodes spéciales binaires (par exemple
__eq__()
,__lt__()
,__add__()
,__rsub__()
, etc.) pour indiquer que l'opération n'est pas mise en œuvre par rapport à l'autre type; peut être retourné par les méthodes spéciales binaires en place (par exemple__imul__()
,__iand__()
, etc.) dans le même but. Sa valeur de vérité est vraie.
Si vous appelez la méthode __eq()__
manuellement plutôt que d'utiliser simplement ==
, Vous devez être prêt à faire face à la possibilité qu'elle puisse renvoyer NotImplemented
et que sa valeur de vérité soit vraie .
Comme vous l'avez déjà compris, None.__eq__("a")
est évalué à NotImplemented
cependant si vous essayez quelque chose comme
if NotImplemented:
print("Yes")
else:
print("No")
le résultat est
oui
cela signifie que la valeur de vérité de NotImplemented
true
Le résultat de la question est donc évident:
None.__eq__(something)
donne NotImplemented
Et bool(NotImplemented)
est évalué à True
Donc if None.__eq__("a")
est toujours True
Il renvoie un NotImplemented
, ouais:
>>> None.__eq__('a')
NotImplemented
>>>
Mais si vous regardez ceci:
>>> bool(NotImplemented)
True
>>>
NotImplemented
est en fait une valeur véridique, c'est pourquoi il renvoie b
, tout ce qui est True
passera, tout ce qui est False
ne le sera pas.
Vous devez vérifier s'il s'agit de True
, alors soyez plus méfiant, comme vous le voyez:
>>> NotImplemented == True
False
>>>
Vous feriez donc:
>>> if None.__eq__('a') == True:
print('b')
>>>
Et comme vous le voyez, cela ne retournerait rien.