2010-10-23 22 views
71

Quando si implementa una classe con proprietà multiple (come nell'esempio di esempio di seguito), qual è il modo migliore per gestire l'hashing?Come implementare una buona funzione __hash__ in python

Immagino che il __eq__ e il __hash__ debbano essere coerenti, ma come implementare una funzione di hash corretta in grado di gestire tutte le proprietà?

class AClass: 
    def __init__(self): 
     self.a = None 
     self.b = None 

    def __eq__(self, other): 
     return other and self.a == other.a and self.b == other.b 

    def __ne__(self, other): 
    return not self.__eq__(other) 

    def __hash__(self): 
     return hash((self.a, self.b)) 

ho letto su this question che tuple sono hashable, quindi mi chiedevo se qualcosa di simile l'esempio precedente era ragionevole. È?

+3

Assicurati di usare 'hash()' su una tupla con esattamente gli elementi che vengono confrontati in '__eq __()' e gli amici (esattamente come hai fatto tu) e sei a posto. – Feuermurmel

+1

Definito duplicato di [Qual è un modo corretto e valido per implementare \ _ \ _ hash \ _ \ _()?] (Http://stackoverflow.com/questions/2909106/whats-a-correct-and-good-way- to-implement-hash) –

risposta

52

__hash__ deve restituire lo stesso valore per gli oggetti che sono uguali. Inoltre, non dovrebbe cambiare durante la vita dell'oggetto; generalmente lo si implementa solo per oggetti immutabili.

Un'implementazione banale sarebbe a solo return 0. Questo è sempre corretto, ma si comporta male.

La soluzione, restituendo l'hash di una tupla di proprietà, è buona. Tuttavia, tieni presente che non è necessario elencare tutte le proprietà confrontate in __eq__ nella tupla. Se alcune proprietà di solito hanno lo stesso valore per oggetti ineguali, basta lasciarli fuori. Non rendere il calcolo hash più costoso di quello che deve essere.

Modifica: mi raccomando di non usare xor per mescolare gli hash in generale. Quando due proprietà differenti hanno lo stesso valore, avranno lo stesso hash e con xor queste si annulleranno a vicenda. Le tuple usano un calcolo più complesso per mescolare gli hash, vedere tuplehash in tupleobject.c.

+3

Come hai detto di solito le funzioni di hash ha senso solo per oggetti immutabili. Quindi è possibile calcolare il valore hash una volta in '__init__'. –

+2

+1 per la funzione di hash 'return 0' - Ho sempre pensato che qualsiasi altra cosa sia l'ottimizzazione prematura :-). (Sto solo scherzando). –

+2

@ BjörnPollex Invece di farlo in '__init__', puoi semplicemente memorizzare il valore in' __hash__'. In questo modo se '__hash__' non viene mai chiamato, non hai perso tempo o memoria. Presumo che controllare se il valore è stato già memorizzato nella cache non è costoso vero? (Non sono sicuro se sia il migliore attraverso l'eccezione o esplicito 'if'). – max

9

documentazione per object.__hash__(self)

L'unica proprietà richiesta è che gli oggetti che risultano uguali hanno lo stesso valore di hash; si consiglia di mescolare in qualche modo (ad esempio usando esclusivi o) i valori hash per i componenti dell'oggetto che svolgono anche una parte nel confronto degli oggetti.

def __hash__(self): 
    return hash(self.a)^hash(self.b) 
+2

Funzionerà, ma è male che se scambiate 'self.a' e' self.b', otterrete lo stesso hash mentre sarà l'altro "oggetto". – eigenein

+0

"in qualche modo si mischiano insieme (ad esempio usando esclusivi o" è un insieme di requisiti piuttosto flessibile. Se conta davvero, allora '(hash (self.a) << 1)^hash (self.b)' potrebbe essere migliore. nessuna risposta generale, solo una linea guida generale che deve essere modificata in base all'applicazione specifica –

+0

@eigenein se in molti casi, è un vantaggio che l'hash è invariato quando l'ordine è cambiato.Se provi ad hash a 'dict' o 'set', l'hash che dipende dall'ordine di iterazione non è valido.OTOH, l'hash che causa semplicemente collisioni extra una volta ogni tanto è, nel peggiore dei casi, inefficiente – max

10

E 'pericoloso scrivere

def __eq__(self, other): 
    return other and self.a == other.a and self.b == other.b 

perché se i tuoi RHS (vale a dire, other) oggetto restituisce Boolean false, non sarà mai confrontare come uguale a niente!

Inoltre, è possibile ricontrollare se other appartiene alla classe o sottoclasse di AClass. In caso contrario, riceverai l'eccezione AttributeError o un falso positivo (se l'altra classe ha gli stessi attributi con i valori corrispondenti). Quindi mi sento di raccomandare a riscrivere __eq__ come:

def __eq__(self, other): 
    return isinstance(other, self.__class__) and self.a == other.a and self.b == other.b 

Se per caso si vuole un confronto insolitamente flessibile, che si confronta attraverso le classi non correlate finché attributi partita per nome, si sarebbe ancora voglia di almeno evitare di AttributeError e controllare che other non abbia attributi aggiuntivi. Il modo in cui lo fai dipende dalla situazione (poiché non esiste un modo standard per trovare tutti gli attributi di un oggetto).

+0

Informazioni utili, ma non correlate alla domanda principale sull'hashing. –

Problemi correlati