2012-02-13 11 views
8

considerare questo frammento:Python 2: significato diverso della 'in' parola chiave per i set e liste

class SomeClass(object): 

    def __init__(self, someattribute="somevalue"): 
     self.someattribute = someattribute 

    def __eq__(self, other): 
     return self.someattribute == other.someattribute 

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

list_of_objects = [SomeClass()] 
print(SomeClass() in list_of_objects) 

set_of_objects = set([SomeClass()]) 
print(SomeClass() in set_of_objects) 

che valuta a:

True 
False 

Qualcuno può spiegare perché il 'in' parola chiave ha un significato diverso per insiemi ed elenchi? Mi sarei aspettato che entrambi restituissero True, specialmente quando il tipo in prova ha definito i metodi di uguaglianza.

+1

Vedere http://stackoverflow.com/questions/7549709/unexpected-behavior-for-python-set-contains .. incidentalmente, in Python 3 l'esecuzione di questo codice suggerisce che cosa sta succedendo: "TypeError: tipo non disattivabile: ' SomeClass '" – DSM

+0

Grazie a tutti, ora è tutto chiaro. – mskel

+0

BTW, sai che il tuo 'someattribute' qui è un attributo ** class ** e non un ** instance **, giusto? Hai * sentito * di "__init__", giusto? –

risposta

15

Il significato è lo stesso, ma l'implementazione è diversa. Gli elenchi esaminano semplicemente ogni oggetto, controllando l'uguaglianza, quindi funziona per la tua classe. Imposta prima gli hash degli oggetti e, se non implementano correttamente l'hash, l'insieme sembra non funzionare.

La classe definisce __eq__, ma non definisce __hash__ e pertanto non funzionerà correttamente per i set o come chiavi dei dizionari. La regola per __eq__ e __hash__ è che due oggetti che __eq__ come True devono avere uguali hash. Per impostazione predefinita, hash degli oggetti in base al loro indirizzo di memoria. Quindi i tuoi due oggetti che sono uguali alla tua definizione non forniscono lo stesso hash, quindi infrangono la regola su __eq__ e __hash__.

Se si fornisce un'implementazione __hash__, funzionerà correttamente. Per il vostro codice di esempio, potrebbe essere:

def __hash__(self): 
    return hash(self.someattribute) 
+4

Questa è una delle cose che Python 3 gestisce in modo più chiaro: si rifiuterà di creare un set di qualsiasi oggetto che non ha un '__hash __()'. Python 2 ha un predefinito '__hash __()' che riflette l'identità dell'oggetto piuttosto che l'uguaglianza. – lvc

+0

In realtà, ciò che è successo è che le classi classiche si comportavano allo stesso modo (non definendo un metodo '__hash__' si avrebbe un default che generasse un TypeError se si definisse' __cmp__' e/o '__eq__',) ma poi nuovo-stile sono state introdotte le classi (in Python 2.2) e il comportamento non è stato copiato correttamente. La supervisione è stata persa per un numero sufficiente di rilasci che, cambiandolo, avrebbe potenzialmente violato troppo il codice, quindi il suo fixing è stato ritardato fino a Python 3. –

1

Definire __hash__() metodo che corrisponde al metodo __eq__(). Example.

3

In quasi tutte le implementazioni di hashtable, incluso Python, se si esegue l'override del metodo di uguaglianza, è necessario sovrascrivere il metodo di hashing (in Python, questo è __hash__). L'operatore in per gli elenchi controlla semplicemente l'uguaglianza con ogni elemento della lista, che l'operatore in imposta per primo gli hash dell'oggetto che si sta cercando, verifica la presenza di un oggetto nello slot della tabella hash e quindi controlla l'uguaglianza se c'è qualcosa nello slot. Pertanto, se si esegue l'override di __eq__ senza eseguire l'override di __hash__, non è possibile garantire che l'operatore in per i set esegua il check-in nello slot corretto.

Problemi correlati