Warum scheint "wenn keine .__ eq __ (" a ")" als wahr (aber nicht ganz) zu bewerten?

146

Wenn Sie die folgende Anweisung in Python 3.7 ausführen, wird sie (nach meinen Tests) gedruckt b:

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

Allerdings None.__eq__("a")bewertet zu NotImplemented.

Natürlich "a".__eq__("a")bewertet Trueund "b".__eq__("a")bewertet False.

Ich habe dies zunächst beim Testen des Rückgabewerts einer Funktion festgestellt, im zweiten Fall jedoch nichts zurückgegeben - daher wurde die Funktion zurückgegeben None.

Was ist denn hier los?

Der KI-Architekt
quelle

Antworten:

178

Dies ist ein gutes Beispiel dafür, warum die __dunder__Methoden nicht direkt verwendet werden sollten, da sie häufig keinen geeigneten Ersatz für ihre äquivalenten Operatoren darstellen. Sie sollten ==stattdessen den Operator für Gleichheitsvergleiche verwenden oder in diesem speziellen Fall bei der Suche nach Noneverwenden is(für weitere Informationen zum Ende der Antwort springen).

Du hast gemacht

None.__eq__('a')
# NotImplemented

Welche Renditen NotImplementedseit dem Vergleich der Typen unterschiedlich sind. Stellen Sie sich ein anderes Beispiel vor, in dem zwei Objekte mit unterschiedlichen Typen auf diese Weise verglichen werden, z. B. 1und 'a'. Das Tun (1).__eq__('a')ist auch nicht korrekt und wird zurückkehren NotImplemented. Der richtige Weg, um diese beiden Werte auf Gleichheit zu vergleichen, wäre

1 == 'a'
# False

Was hier passiert ist

  1. Zunächst (1).__eq__('a')wird versucht, was zurückkehrt NotImplemented. Dies zeigt an, dass der Vorgang nicht unterstützt wird
  2. 'a'.__eq__(1)heißt, was auch das gleiche zurückgibt NotImplemented. So,
  3. Die Objekte werden so behandelt, als wären sie nicht identisch, und Falsewerden zurückgegeben.

Hier ist eine nette kleine MCVE, die einige benutzerdefinierte Klassen verwendet, um zu veranschaulichen, wie dies geschieht:

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

Das erklärt natürlich nicht, warum die Operation true zurückgibt. Dies liegt daran, dass NotImplementedes sich tatsächlich um einen wahrheitsgemäßen Wert handelt:

bool(None.__eq__("a"))
# True

Gleich wie,

bool(NotImplemented)
# True

Weitere Informationen darüber, welche Werte als wahr und falsch angesehen werden, finden Sie im Abschnitt Dokumente zum Testen von Wahrheitswerten sowie in dieser Antwort . Es ist erwähnenswert , dass NotImplementedtruthy ist, aber es wäre eine andere Geschichte die Klasse eine definierte hatte gewesen __bool__oder __len__Verfahren , dass zurückgegeben Falseoder 0sind.


Wenn Sie das funktionale Äquivalent des ==Operators wünschen , verwenden Sie operator.eq:

import operator
operator.eq(1, 'a')
# False

Wie bereits erwähnt, verwenden Sie für dieses spezielle Szenario , in dem Sie suchen None, Folgendes is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

Das funktionale Äquivalent dazu ist operator.is_:

operator.is_(var2, None)
# True

Noneist ein spezielles Objekt, und zu jedem Zeitpunkt ist nur 1 Version im Speicher vorhanden. IOW, es ist der einzige Singleton der NoneTypeKlasse (aber dasselbe Objekt kann eine beliebige Anzahl von Referenzen haben). Die PEP8-Richtlinien machen dies deutlich:

Vergleiche mit Singletons wie Nonesollten immer mit isoder is notniemals mit den Gleichheitsoperatoren durchgeführt werden.

Zusammenfassend ist für Singletons wie Noneeine Referenzprüfung mit isbesser geeignet, obwohl beide ==und gut isfunktionieren.

cs95
quelle
33

Das Ergebnis, das Sie sehen, wird durch die Tatsache verursacht, dass

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

bewertet NotImplementedund NotImplementedder Wahrheitswert ist dokumentiert als True:

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

Sonderwert, der durch die binären speziellen Methoden zurückgegeben werden soll (z __eq__(), __lt__(), __add__(), __rsub__(), etc.) , um anzuzeigen , dass die Operation nicht in Bezug auf die andere Art durchgeführt wird; kann durch die in-place binären speziellen Methoden (zB zurückgeführt wird __imul__(), __iand__()usw.) für den gleichen Zweck. Sein Wahrheitswert ist wahr.

Wenn Sie die __eq()__Methode manuell aufrufen und nicht nur verwenden ==, müssen Sie darauf vorbereitet sein, mit der Möglichkeit umzugehen, dass sie möglicherweise zurückkehrt NotImplementedund dass ihr Wahrheitswert wahr ist.

Mark Meyer
quelle
16

Wie Sie bereits gedacht haben, None.__eq__("a")ergibt sich NotImplementedjedoch, wenn Sie so etwas versuchen

if NotImplemented:
    print("Yes")
else:
    print("No")

Das Ergebnis ist

Ja

dies bedeutet, dass der Wahrheitswert von NotImplemented true

Daher ist das Ergebnis der Frage offensichtlich:

None.__eq__(something) ergibt NotImplemented

Und bool(NotImplemented)ergibt True

So if None.__eq__("a")ist es immer wahr

Kanjiu
quelle
1

Warum?

Es gibt ein NotImplemented, ja:

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

Aber wenn Sie sich das ansehen:

>>> bool(NotImplemented)
True
>>> 

NotImplementedist eigentlich ein wahrer Wert, deshalb kehrt er zurück b, alles, was vergeht True, alles, Falsewas nicht vergeht .

Wie man es löst?

Sie müssen überprüfen, ob Truedies der Fall ist . Seien Sie also misstrauischer, wie Sie sehen:

>>> NotImplemented == True
False
>>> 

Also würden Sie tun:

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


>>> 

Und wie Sie sehen, würde es nichts zurückgeben.

U10-Vorwärts
quelle
1
visuell
klarste
1
:) "lohnende Ergänzung" fängt nicht ganz ein, was ich sagen wollte (wie Sie sehen) - vielleicht wollte ich "verspätete Exzellenz"
Prost
@scharfmn ja? Ich bin gespannt, was Ihrer Meinung nach diese Antwort hinzufügt, die noch nicht behandelt wurde.
CS95
Irgendwie sorgen die visuellen / repl-Dinge hier für Klarheit - vollständige Demo
scharfmn
@scharfmn ... Was die akzeptierte Antwort auch hat, obwohl die Eingabeaufforderungen entfernt wurden. Haben Sie nur deshalb positiv gestimmt, weil die Eingabeaufforderungen des Terminals träge belassen wurden?
CS95