John Millikin ha proposto una soluzione simile a questa:
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))
Il problema con questa soluzione è che hash(A(a, b, c)) == hash((a, b, c))
. In altre parole, l'hash si scontra con quello della tupla dei suoi membri chiave. Forse questo non importa molto spesso in pratica?
Aggiornamento: i documenti di Python ora raccomandano di usare una tupla come nell'esempio sopra. Si noti che la documentazione afferma
L'unica proprietà richiesta è che gli oggetti che si equivalgono hanno lo stesso valore hash
Si noti che non è vero il contrario. Gli oggetti che non si equivalgono possono avere lo stesso valore hash. Una tale collisione hash non farà sì che un oggetto ne sostituisca un altro quando viene usato come chiave dict o imposta elemento purché gli oggetti non siano uguali .
Soluzione obsoleta / non valida
La documentazione di Python su__hash__
suggerisce di combinare gli hash dei sottocomponenti usando qualcosa come XOR , che ci dà questo:
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)))
Aggiornamento: come sottolinea Blckknght, cambiare l'ordine di a, b e c potrebbe causare problemi. Ho aggiunto un ulteriore ^ hash((self._a, self._b, self._c))
per catturare l'ordine dei valori sottoposti a hash. Questo finale ^ hash(...)
può essere rimosso se i valori combinati non possono essere riorganizzati (ad esempio, se hanno tipi diversi e quindi il valore di _a
non verrà mai assegnato a _b
o _c
, ecc.).
__key
funzione, questo è veloce quanto qualsiasi hash può essere. Certo, se si sa che gli attributi sono numeri interi e non ce ne sono troppi, suppongo che potresti potenzialmente correre leggermente più veloce con un po 'di hash roll a casa, ma probabilmente non sarebbe altrettanto ben distribuito.hash((self.attr_a, self.attr_b, self.attr_c))
sarà sorprendentemente veloce (e corretto ), poiché la creazione di piccoletuple
s è appositamente ottimizzata e spinge il lavoro di ottenere e combinare gli hash con i builtin C, che è in genere più veloce del codice di livello Python.