John Millikin schlug eine ähnliche Lösung vor:
class A(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
return (isinstance(othr, type(self))
and (self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
def __hash__(self):
return hash((self._a, self._b, self._c))
Das Problem bei dieser Lösung ist, dass die hash(A(a, b, c)) == hash((a, b, c))
. Mit anderen Worten, der Hash kollidiert mit dem des Tupels seiner Schlüsselmitglieder. Vielleicht spielt das in der Praxis keine Rolle?
Update: In den Python-Dokumenten wird jetzt empfohlen, ein Tupel wie im obigen Beispiel zu verwenden. Beachten Sie, dass in der Dokumentation angegeben ist
Die einzige erforderliche Eigenschaft ist, dass Objekte, die gleich sind, denselben Hashwert haben
Beachten Sie, dass das Gegenteil nicht der Fall ist. Objekte, die nicht gleich sind, haben möglicherweise denselben Hashwert. Eine solche Hash-Kollision führt nicht dazu, dass ein Objekt ein anderes ersetzt, wenn es als Diktatschlüssel oder Set-Element verwendet wird , solange die Objekte nicht auch gleich sind .
Veraltete / schlechte Lösung
Die Python-Dokumentation zu__hash__
schlägt vor, die Hashes der Unterkomponenten mit etwas wie XOR zu kombinieren , was uns Folgendes gibt:
class B(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
if isinstance(othr, type(self)):
return ((self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
return NotImplemented
def __hash__(self):
return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
hash((self._a, self._b, self._c)))
Update: Wie Blckknght betont, kann das Ändern der Reihenfolge von a, b und c zu Problemen führen. Ich habe eine zusätzliche hinzugefügt ^ hash((self._a, self._b, self._c))
, um die Reihenfolge der gehashten Werte zu erfassen. Dieses Finale ^ hash(...)
kann entfernt werden, wenn die zu kombinierenden Werte nicht neu angeordnet werden können (z. B. wenn sie unterschiedliche Typen haben und daher der Wert von _a
niemals _b
oder _c
usw. zugewiesen wird).
__key
Funktion ist dies ungefähr so schnell wie jeder Hash sein kann. Sicher, wenn bekannt ist, dass die Attribute Ganzzahlen sind und es nicht zu viele davon gibt, könnten Sie mit einem selbst gerollten Hash möglicherweise etwas schneller laufen , aber es wäre wahrscheinlich nicht so gut verteilt.hash((self.attr_a, self.attr_b, self.attr_c))
wird überraschend schnell (und korrekt ) sein, da die Erstellung kleinertuple
s speziell optimiert ist und die Arbeit des Abrufens und Kombinierens von Hashes zu C-Builtins vorantreibt, was normalerweise schneller als Python-Code ist.