Betrachten Sie dieses einfache Problem:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Python verwendet also standardmäßig die Objektbezeichner für Vergleichsoperationen:
id(n1) # 140400634555856
id(n2) # 140400634555920
Das Überschreiben der __eq__
Funktion scheint das Problem zu lösen:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
Unter Python 2 immer daran denken, die Option __ne__
auch die Funktion, da die Dokumentation Staaten:
Es gibt keine impliziten Beziehungen zwischen den Vergleichsoperatoren. Die Wahrheit von x==y
impliziert nicht, dass x!=y
falsch ist. Wenn also Definition von __eq__()
sollte man auch definieren __ne__()
so dass die Operatoren sich wie erwartet verhalten werden.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
Unter Python 3 ist dies nicht mehr notwendig, da die Dokumentation Staaten:
Standardmäßig, __ne__()
Delegierte zu __eq__()
und kehrt das Ergebnis um es sei denn, es ist NotImplemented
. Es gibt keine anderen impliziten Beziehungen zwischen den Vergleichsoperatoren, zum Beispiel die Wahrheit von (x<y or x==y)
impliziert nicht x<=y
.
Aber das löst nicht alle unsere Probleme. Fügen wir eine Unterklasse hinzu:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Anmerkung: Python 2 hat zwei Arten von Klassen:
-
klassischer Stil (ou altmodisch ) Klassen, die no erben von object
und die deklariert sind als class A:
, class A():
o class A(B):
donde B
ist eine Klasse im klassischen Stil;
-
neuartiges Klassen, die erben von object
und die deklariert sind als class A(object)
o class A(B):
donde B
ist eine Klasse des neuen Stils. Python 3 hat nur New-Style-Klassen, die deklariert werden als class A:
, class A(object):
o class A(B):
.
Bei Klassen im klassischen Stil ruft eine Vergleichsoperation immer die Methode des ersten Operanden auf, während sie bei Klassen im neuen Stil immer die Methode des Operanden der Unterklasse aufruft, unabhängig von der Reihenfolge der Operanden .
Wenn also hier Number
ist eine Klasse im klassischen Stil:
n1 == n3
llama a n1.__eq__
;
n3 == n1
llama a n3.__eq__
;
n1 != n3
llama a n1.__ne__
;
n3 != n1
llama a n3.__ne__
.
Und wenn Number
ist eine Klasse des neuen Stils:
- beide
n1 == n3
y n3 == n1
aufrufen n3.__eq__
;
- beide
n1 != n3
y n3 != n1
aufrufen n3.__ne__
.
Um das Problem der Nicht-Kommutativität der ==
y !=
Operatoren für Python-2-Klassen im klassischen Stil, die __eq__
y __ne__
Methoden sollten die NotImplemented
Wert, wenn ein Operandentyp nicht unterstützt wird. Die Website Dokumentation definiert die NotImplemented
Wert als:
Numerische Methoden und umfangreiche Vergleichsmethoden können diesen Wert zurückgeben, wenn sie die Operation für die angegebenen Operanden nicht implementieren. (Die Interpreter versucht dann die reflektierte Operation oder eine andere Fallback, abhängig vom Operator.) Sein Wahrheitswert ist true.
In diesem Fall delegiert der Betreiber die Vergleichsoperation an den reflektierte Methode der andere Operand. Die Website Dokumentation definiert reflektierte Methoden als:
Es gibt keine Versionen dieser Methoden mit vertauschten Argumenten (für die Verwendung von wenn das linke Argument die Operation nicht unterstützt, aber das rechte Argument die Operation unterstützt); stattdessen, __lt__()
y __gt__()
sind die gegenseitigen Reflexion, __le__()
y __ge__()
sind das Spiegelbild des jeweils anderen und __eq__()
y __ne__()
sind ihr eigenes Spiegelbild.
Das Ergebnis sieht wie folgt aus:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
Rücksendung der NotImplemented
Wert anstelle von False
ist auch für neuartige Klassen das Richtige, wenn Kommutativität der ==
y !=
Operatoren ist erwünscht, wenn die Operanden von unterschiedlichen Typen sind (keine Vererbung).
Sind wir schon da? Nicht ganz. Wie viele eindeutige Nummern haben wir?
len(set([n1, n2, n3])) # 3 -- oops
Sets verwenden die Hashes von Objekten, und standardmäßig gibt Python den Hash des Bezeichners des Objekts zurück. Lassen Sie uns versuchen, dies zu überschreiben:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
Das Endergebnis sieht wie folgt aus (ich habe am Ende einige Behauptungen zur Validierung hinzugefügt):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2