web-dev-qa-db-fra.com

Pourquoi `if None .__ eq __ (" a ")` semble correspondre à True (mais pas tout à fait)?

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?

146
The AI Architect

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. Tout d'abord, (1).__eq__('a') Est essayé, ce qui renvoie NotImplemented. Cela indique que l'opération n'est pas prise en charge, donc
  2. 'a'.__eq__(1) est appelée, ce qui renvoie également le même NotImplemented. Alors,
  3. Les objets sont traités comme s'ils n'étaient pas identiques et 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 avec is ou is 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.

177
cs95

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 .

33
Mark Meyer

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 NotImplementedtrue

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

16
Kanjiu

Pourquoi?

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.

Comment le résoudre?

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.

1
U10-Forward