Typen, die `__eq__` definieren, sind nicht zerlegbar?

74

Ich hatte einen seltsamen Fehler beim Portieren einer Funktion auf den Python 3.1-Zweig meines Programms. Ich habe es auf folgende Hypothese eingegrenzt:

Im Gegensatz zu Python 2.x ist ein Objekt in Python 3.x __eq__automatisch nicht verwertbar, wenn es über eine Methode verfügt.

Ist das wahr?

Folgendes passiert in Python 3.1:

>>> class O(object):
...     def __eq__(self, other):
...         return 'whatever'
...
>>> o = O()
>>> d = {o: 0}
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    d = {o: 0}
TypeError: unhashable type: 'O'

Die folgende Frage lautet: Wie löse ich mein persönliches Problem? Ich habe ein Objekt, ChangeTrackerdas ein Objekt speichert WeakKeyDictionary, das auf mehrere Objekte verweist und für jedes den Wert ihres Pickle Dump zu einem bestimmten Zeitpunkt in der Vergangenheit angibt. Jedes Mal, wenn ein vorhandenes Objekt eingecheckt wird, gibt der Änderungs-Tracker an, ob die neue Gurke mit der alten identisch ist, und gibt daher an, ob sich das Objekt in der Zwischenzeit geändert hat. Das Problem ist, dass ich jetzt nicht einmal überprüfen kann, ob sich das angegebene Objekt in der Bibliothek befindet, da dadurch eine Ausnahme ausgelöst wird, dass das Objekt nicht verwertbar ist. (Weil es eine __eq__Methode gibt.) Wie kann ich das umgehen?

Ram Rachum
quelle
3
Was passiert, wenn Sie eine __hash__Methode angeben?
ndim
Ich bin überrascht zu sehen, dass niemand darüber diskutiert, warum dies kein Problem für Python 2 ist.
Nehem

Antworten:

77

Ja, wenn Sie definieren __eq__, verschwindet die Standardeinstellung __hash__(nämlich das Hashing der Adresse des Objekts im Speicher). Dies ist wichtig, da das Hashing mit der Gleichheit übereinstimmen muss: Gleiche Objekte müssen dasselbe hashen.

Die Lösung ist einfach: Definieren Sie einfach __hash__zusammen mit dem Definieren __eq__.

Martin v. Löwis
quelle
Lassen Sie mich für die Nachwelt hinzufügen: Ich definierte __hash__als return id(self).
Ram Rachum
28
@ cool-RR: Die Verwendung von id(self)in ist __hash__()sicherlich falsch, es sei denn, Sie haben __eq__()einen Vergleich nach Objektidentität definiert (was bedeutungslos erscheint). Wie wäre __eq__()es, wenn Sie Ihre Frage mit einer echten Implementierung aktualisieren , damit wir vorschlagen können, sie zu __hash__()ergänzen?
Denis Otkidach
Ich habe auch versucht, dieses Problem zu lösen. Ich kam mit diesem: def __eq__(): return self.__dict__ == other.__dict__ und def __hash__(): return hash(self.__dict__.values())
Turtles Are Cute
5
Dies ist keine gute Antwort - es ist nicht nur der Standard-Hash, der verschwindet - es ist jeder geerbte Hash .
Eric
26

Dieser Absatz von http://docs.python.org/3.1/reference/datamodel.html#object. Hash

Wenn eine Klasse, die überschreibt __eq__() , die Implementierung __hash__()einer übergeordneten Klasse beibehalten muss, muss dies dem Interpreter durch Festlegen explizit mitgeteilt werden __hash__ = <ParentClass>.__hash__. Andernfalls wird die Vererbung von __hash__()blockiert, als wäre __hash__sie explizit auf Keine gesetzt worden.

newacct
quelle
3

Überprüfen Sie das Python 3-Handbuch auf object.__hash__:

Wenn eine Klasse keine __eq__()Methode definiert , sollte sie auch keine __hash__()Operation definieren . Wenn es definiert, __eq__()aber nicht __hash__(), können seine Instanzen nicht als Elemente in Hash-Sammlungen verwendet werden.

Der Schwerpunkt liegt bei mir.

Wenn Sie faul sein möchten, können Sie einfach definieren, __hash__(self)um zurückzukehren id(self):

Benutzerdefinierte Klassen haben __eq__()und __hash__()Methoden standardmäßig; Mit ihnen vergleichen sich alle Objekte ungleich (außer mit sich selbst) und x.__hash__()kehren zurück id(x).

Mark Rushakoff
quelle
5
Sicherlich ist der einzige Grund für die Überladung des Gleichheitsoperators, dass zwei verschiedene Objekte manchmal gleich sind (wenn nicht, dann stören Sie sich nicht daran, ihn zu überladen). In diesem Fall ist die return id(self)Hash-Funktion fehlerhaft (gleiche Objekte müssen gleich hashen, siehe Martins Antwort). Die Hash-Funktion "Es ist mir egal" wäre return 1. Dies ist einfach und erfüllt alle Bedingungen (es ist nur sehr ineffizient!)
Scott Griffiths
1

Ich bin kein Python-Experte, aber wäre es nicht sinnvoll, wenn Sie eine EQ-Methode definieren, müssen Sie auch eine Hash-Methode definieren (die den Hash-Wert für ein Objekt berechnet). Andernfalls den Hashing-Mechanismus Ich würde nicht wissen, ob es dasselbe Objekt oder ein anderes Objekt mit genau demselben Hashwert trifft. Umgekehrt würde es wahrscheinlich dazu führen, dass unterschiedliche Hash-Werte für Objekte berechnet werden, die von Ihrer __eq__Methode als gleich angesehen werden .

Ich habe keine Ahnung, wie diese Hash-Funktion __hash__vielleicht heißt? :) :)

falstro
quelle
6
Zu Ihrer Information : Ich habe dies vor einiger Zeit gezeichnet ( mindmeister.com/10510492/python-underscore ): eine Mindmap aller Unterstrichmethoden in Python.
Jldupont
3
Es ist eigentlich umgekehrt. Wenn zwei Objekte den gleichen Hashwert haben, aber nicht gleich sind, ist das in Ordnung. Der "Hashing-Mechanismus" (dh das Wörterbuch) überprüft zuerst den Hash und vergleicht bei demselben Hash auch die Gleichheit. Das eigentliche Problem tritt umgekehrt auf: Objekte hashen unterschiedlich, vergleichen aber immer noch das gleiche. Das Wörterbuch sollte sie finden, tut es aber nicht (oder Sie erhalten möglicherweise doppelte Schlüssel im Wörterbuch).
Martin v. Löwis
@ Martin v. Löwis: Ich habe das erkannt und am Ende hinzugefügt, oder gibt es einen subtilen Unterschied zu dem, was ich gesagt habe?
Falstro