Beim Schreiben von benutzerdefinierten Klassen ist es häufig wichtig, die Äquivalenz mithilfe der Operatoren ==
und zuzulassen !=
. In Python wird dies durch die Implementierung der jeweiligen __eq__
bzw. der __ne__
speziellen Methode ermöglicht. Der einfachste Weg, dies zu tun, ist die folgende Methode:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
Kennen Sie elegantere Mittel, um dies zu tun? Kennen Sie besondere Nachteile bei der Verwendung der oben genannten Methode zum Vergleichen von __dict__
s?
Hinweis : Ein bisschen Klarstellung - wenn __eq__
und __ne__
undefiniert, finden Sie dieses Verhalten:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
Das heißt, a == b
bewertet, False
weil es wirklich läuft a is b
, einen Identitätstest (dh "Ist a
das gleiche Objekt wie b
?").
Wenn __eq__
und __ne__
definiert sind, finden Sie dieses Verhalten (das ist das, nach dem wir suchen):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
quelle
is
Operator hat, um die Objektidentität vom Wertevergleich zu unterscheiden.Antworten:
Betrachten Sie dieses einfache Problem:
Daher verwendet Python standardmäßig die Objektkennungen für Vergleichsoperationen:
Das Überschreiben der
__eq__
Funktion scheint das Problem zu lösen:Denken Sie in Python 2 immer daran, die
__ne__
Funktion ebenfalls zu überschreiben , wie in der Dokumentation angegeben :In Python 3 ist dies nicht mehr erforderlich, wie in der Dokumentation angegeben :
Das löst aber nicht alle unsere Probleme. Fügen wir eine Unterklasse hinzu:
Hinweis: Python 2 hat zwei Arten von Klassen:
Klassen im klassischen Stil (oder im alten Stil ), die nicht erben
object
und als deklariertclass A:
sindclass A():
oderclass A(B):
woB
sich eine Klasse im klassischen Stil befindet;Klassen neuen Stils , die voneiner Klasse neuen Stilserben
object
und alsclass A(object)
oderclass A(B):
wodeklariert sindB
. Python 3 enthält nur Klassen neuen Stils, die alsoderdeklariertclass A:
sind.class A(object):
class A(B):
Bei Klassen im klassischen Stil ruft eine Vergleichsoperation immer die Methode des ersten Operanden auf, während bei Klassen im neuen Stil unabhängig von der Reihenfolge der Operanden immer die Methode des Operanden der Unterklasse aufgerufen wird .
Also hier, wenn
Number
es sich um eine Klasse im klassischen Stil handelt:n1 == n3
Anrufen1.__eq__
;n3 == n1
Anrufen3.__eq__
;n1 != n3
Anrufen1.__ne__
;n3 != n1
Anrufen3.__ne__
.Und wenn
Number
es sich um eine neue Klasse handelt:n1 == n3
undn3 == n1
anrufenn3.__eq__
;n1 != n3
undn3 != n1
anrufenn3.__ne__
.Um das Nichtkommutativitätsproblem der Operatoren
==
und!=
für Python 2-Klassen im klassischen Stil zu beheben , sollten die Methoden__eq__
und__ne__
denNotImplemented
Wert zurückgeben, wenn ein Operandentyp nicht unterstützt wird. Die Dokumentation definiert denNotImplemented
Wert als:In diesem Fall delegiert der Operator die Vergleichsoperation an die reflektierte Methode des anderen Operanden. In der Dokumentation werden reflektierte Methoden wie folgt definiert:
Das Ergebnis sieht folgendermaßen aus:
Die Rückgabe des
NotImplemented
Werts anstelle vonFalse
ist auch für Klassen neuen Stils das Richtige, wenn die Kommutativität der Operatoren==
und!=
gewünscht wird, wenn die Operanden nicht verwandte Typen haben (keine Vererbung).Sind wir schon da? Nicht ganz. Wie viele eindeutige Nummern haben wir?
Sets verwenden die Hashes von Objekten, und Python gibt standardmäßig den Hash des Bezeichners des Objekts zurück. Versuchen wir es zu überschreiben:
Das Endergebnis sieht folgendermaßen aus (ich habe am Ende einige Aussagen zur Validierung hinzugefügt):
quelle
hash(tuple(sorted(self.__dict__.items())))
funktioniert nicht, wenn sich unter den Werten von nicht hashbare Objekte befindenself.__dict__
(dh wenn eines der Attribute des Objekts beispielsweise auf a gesetzt istlist
).__ne__
verwenden==
statt__eq__
.__ne__
mehr durchgeführt werden: "Standardmäßig wird das Ergebnis an das Ergebnis__ne__()
delegiert__eq__()
und invertiert, sofern dies nicht der Fall ist.NotImplemented
" 2. Wenn man noch implementieren möchte,__ne__
ist eine allgemeinere Implementierung (die von Python 3, glaube ich) :x = self.__eq__(other); if x is NotImplemented: return x; else: return not x
. 3. Die gegebenen__eq__
und__ne__
Implementierungen sind suboptimal:if isinstance(other, type(self)):
gibt 22__eq__
und 10__ne__
Aufrufe, währendif isinstance(self, type(other)):
16__eq__
und 6__ne__
Aufrufe geben würden .Sie müssen mit der Vererbung vorsichtig sein:
Überprüfen Sie die Typen strenger wie folgt:
Abgesehen davon wird Ihr Ansatz gut funktionieren, dafür gibt es spezielle Methoden.
quelle
NotImplemented
wie Sie vorschlagen, führt jedoch immer zusuperclass.__eq__(subclass)
dem gewünschten Verhalten.if other is self
. Dies vermeidet den längeren Wörterbuchvergleich und kann eine enorme Ersparnis bedeuten, wenn Objekte als Wörterbuchschlüssel verwendet werden.__hash__()
Die Art, wie Sie beschreiben, ist die Art, wie ich es immer getan habe. Da es vollständig generisch ist, können Sie diese Funktionalität jederzeit in eine Mixin-Klasse aufteilen und in Klassen erben, in denen Sie diese Funktionalität wünschen.
quelle
other
es sich um eine Unterklasse von handeltself.__class__
.__dict__
Vergleich ist, wenn Sie ein Attribut haben, das Sie bei Ihrer Definition der Gleichheit nicht berücksichtigen möchten (z. B. eine eindeutige Objekt-ID oder Metadaten wie einen Zeitstempel).Keine direkte Antwort, schien aber relevant genug, um angegangen zu werden, da dies gelegentlich ein wenig langwierige Langeweile erspart. Schneiden Sie direkt aus den Dokumenten ...
functools.total_ordering (cls)
Bei einer Klasse, die eine oder mehrere umfangreiche Vergleichsordnungsmethoden definiert, liefert dieser Klassendekorateur den Rest. Dies vereinfacht den Aufwand für die Angabe aller möglichen umfangreichen Vergleichsoperationen:
Die Klasse muss eine von definieren
__lt__()
,__le__()
,__gt__()
, oder__ge__()
. Zusätzlich sollte die Klasse eine__eq__()
Methode bereitstellen .Neu in Version 2.7
quelle
Sie müssen nicht beide überschreiben
__eq__
und__ne__
können nur überschreiben__cmp__
, dies hat jedoch Auswirkungen auf das Ergebnis von == ,! ==, <,> usw.is
Tests für die Objektidentität. Dies bedeutet, dass ais
bTrue
der Fall ist, wenn a und b beide die Referenz auf dasselbe Objekt enthalten. In Python enthalten Sie immer einen Verweis auf ein Objekt in einer Variablen, nicht auf das eigentliche Objekt. Wenn also a b wahr ist, sollten sich die darin enthaltenen Objekte im Wesentlichen an derselben Speicherstelle befinden. Wie und vor allem warum sollten Sie dieses Verhalten außer Kraft setzen?Bearbeiten: Ich wusste nicht, dass
__cmp__
aus Python 3 entfernt wurde, also vermeiden Sie es.quelle
Aus dieser Antwort: https://stackoverflow.com/a/30676267/541136 Ich habe gezeigt, dass es zwar richtig ist,
__ne__
Begriffe zu definieren__eq__
- stattdu solltest benutzen:
quelle
Ich denke, dass die beiden Begriffe, nach denen Sie suchen, Gleichheit (==) und Identität (ist) sind. Zum Beispiel:
quelle
Der 'is'-Test testet die Identität mit der eingebauten' id () '- Funktion, die im Wesentlichen die Speicheradresse des Objekts zurückgibt und daher nicht überladbar ist.
Wenn Sie jedoch die Gleichheit einer Klasse testen möchten, möchten Sie Ihre Tests wahrscheinlich etwas strenger gestalten und nur die Datenattribute in Ihrer Klasse vergleichen:
Dieser Code vergleicht nur Nicht-Funktionsdaten-Mitglieder Ihrer Klasse und überspringt alles Private, was im Allgemeinen gewünscht wird. Im Fall von einfachen alten Python-Objekten habe ich eine Basisklasse, die __init__, __str__, __repr__ und __eq__ implementiert, sodass meine POPO-Objekte nicht die Last all dieser zusätzlichen (und in den meisten Fällen identischen) Logik tragen.
quelle
__eq__
in deklariert wirdCommonEqualityMixin
(siehe die andere Antwort). Ich fand dies besonders nützlich beim Vergleich von Instanzen von Klassen, die von Base in SQLAlchemy abgeleitet wurden. Um nicht zu vergleichen,_sa_instance_state
wechselte ichkey.startswith("__")):
zukey.startswith("_")):
. Ich hatte auch einige Rückreferenzen in ihnen und die Antwort von Algorias erzeugte endlose Rekursion. Deshalb habe ich alle Rückreferenzen'_'
so benannt, dass sie auch beim Vergleich übersprungen werden. HINWEIS: Wechseln Sie in Python 3.xiteritems()
zuitems()
.__dict__
eine Instanz nichts, mit dem begonnen wird,__
es sei denn, es wurde vom Benutzer definiert. Dinge wie__class__
,__init__
etc. sind nicht in der Instanz__dict__
, sondern in seiner Klasse__dict__
. OTOH, die privaten Attribute können leicht damit beginnen__
und sollten wahrscheinlich für verwendet werden__eq__
. Können Sie klarstellen, was genau Sie vermeiden__
wollten, wenn Sie Attribute mit Präfix überspringen ?Anstatt Unterklassen / Mixins zu verwenden, verwende ich gerne einen generischen Klassendekorateur
Verwendungszweck:
quelle
Dies beinhaltet die Kommentare zu Algorias 'Antwort und vergleicht Objekte mit einem einzigen Attribut, da mir das ganze Diktat egal ist.
hasattr(other, "id")
muss wahr sein, aber ich weiß, dass es daran liegt, dass ich es im Konstruktor festgelegt habe.quelle