Pourquoi «si Aucun .__ eq __ (« a »)» semble s'évaluer à Vrai (mais pas tout à fait)?

146

Si vous exécutez l'instruction suivante en Python 3.7, elle sera (d'après mes tests) imprimée b:

if None.__eq__("a"):
    print("b")

Cependant, None.__eq__("a")évalue à NotImplemented.

Naturellement, "a".__eq__("a")évalue Trueet "b".__eq__("a")évalue False.

J'ai d'abord découvert cela en testant la valeur de retour d'une fonction, mais je n'ai rien retourné dans le second cas - donc, la fonction est retournée None.

Que se passe t-il ici?

L'architecte IA
la source

Réponses:

178

Ceci est un excellent exemple de la raison pour laquelle les __dunder__méthodes ne devraient pas être utilisées directement car elles ne sont souvent pas des remplacements appropriés pour leurs opérateurs équivalents; vous devriez utiliser l' ==opérateur à la place pour les comparaisons d'égalité, ou dans ce cas particulier, lors de la vérification None, utilisez is(passez au bas de la réponse pour plus d'informations).

Tu as fait

None.__eq__('a')
# NotImplemented

Quels retours NotImplementedpuisque 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 1et 'a'. Faire (1).__eq__('a')n'est pas non plus correct et reviendra NotImplemented. La bonne façon de comparer ces deux valeurs pour l'égalité serait

1 == 'a'
# False

Ce qui se passe ici est

  1. Tout d'abord, (1).__eq__('a')est essayé, qui revient NotImplemented. Cela indique que l'opération n'est pas prise en charge, donc
  2. 'a'.__eq__(1)est appelé, qui renvoie également le même NotImplemented. Alors,
  3. Les objets sont traités comme s'ils n'étaient pas identiques et Falsesont renvoyés.

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 retourne true. C'est parce que NotImplementedc'est en fait une valeur de vérité:

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 de documentation sur le test de la valeur de vérité , ainsi que cette réponse . Il est intéressant de noter ici que NotImplementedc'est vrai, mais cela aurait été une histoire différente si la classe avait défini une méthode __bool__ou __len__qui retournait Falseou 0respectivement.


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 , dans lequel 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

Noneest un objet spécial, et une seule version existe en mémoire à tout moment. IOW, c'est le seul singleton de la NoneTypeclasse (mais le même objet peut avoir n'importe quel nombre de références). Les directives PEP8 expliquent cela:

Les comparaisons avec des singletons comme Nonedevraient toujours être faites avec isou is not, jamais avec les opérateurs d'égalité.

En résumé, pour les singletons aiment None, une vérification de référence avec isest plus approprié, même si les deux ==et isfonctionnera très bien.

cs95
la source
33

Le résultat que vous voyez est causé par le fait que

None.__eq__("a") # evaluates to NotImplemented

évalue NotImplemented, et NotImplementedla valeur de vérité de est documentée comme étant True:

https://docs.python.org/3/library/constants.html

La valeur spéciale qui devrait être renvoyée par les méthodes spéciales binaires (par exemple __eq__(), __lt__(), __add__(), __rsub__(), etc.) pour indiquer que l'opération ne soit pas mis en œuvre par rapport à l'autre type; peuvent être retournés par les en place binaires méthodes spéciales (par exemple __imul__(), __iand__(), etc.) pour le même but. Sa valeur de vérité est vraie.

Si vous appelez la __eq()__méthode manuellement plutôt que simplement en utilisant ==, vous devez être prêt à faire face à la possibilité qu'elle peut retourner NotImplementedet que sa valeur de vérité soit vraie.

Mark Meyer
la source
16

Comme vous l'avez déjà compris, il None.__eq__("a")évalue NotImplementedcependant 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

Par conséquent, l'issue de la question est évidente:

None.__eq__(something) rendements NotImplemented

Et bool(NotImplemented)évalue à True

Ainsi if None.__eq__("a")est toujours vrai

Kanjiu
la source
1

Pourquoi?

Ça renvoie un NotImplemented, ouais:

>>> None.__eq__('a')
NotImplemented
>>> 

Mais si vous regardez ceci:

>>> bool(NotImplemented)
True
>>> 

NotImplementedest en fait une valeur de vérité, c'est pourquoi elle renvoie b, tout ce qui est Truepassera, tout ce Falsequi ne le sera pas.

Comment le résoudre?

Vous devez vérifier si c'est le cas True, alors soyez plus méfiant, comme vous le voyez:

>>> NotImplemented == True
False
>>> 

Alors vous feriez:

>>> if None.__eq__('a') == True:
    print('b')


>>> 

Et comme vous le voyez, cela ne rapporterait rien.

U10-avant
la source
1
réponse la plus claire visuellement - v ajout utile - merci
scharfmn
1
:) "ajout intéressant" ne reflète pas tout à fait ce que j'essayais de dire (comme vous le voyez) - peut-être que "l'excellence tardive" est ce que je voulais - acclamations
scharfmn
@scharfmn oui? Je suis curieux de savoir ce que vous pensez que cette réponse ajoute qui n'a pas déjà été couvert auparavant.
cs95
en quelque sorte, les choses visuelles / répliques ajoutent de la clarté - démo complète
scharfmn
@scharfmn ... dont la réponse acceptée a également bien que les invites aient été supprimées. Avez-vous voté pour le seul motif que les invites du terminal ont été laissées paresseusement?
cs95