2011-02-04 20 views
146

Cosa devo fare per utilizzare i miei oggetti di un tipo personalizzato come chiavi in ​​un dizionario Python (dove non voglio che "ID oggetto" agisca come chiave), ad es.Oggetto del tipo personalizzato come chiave del dizionario

class MyThing: 
    def __init__(self,name,location,length): 
      self.name = name 
      self.location = location 
      self.length = length 

Vorrei utilizzare MyThing come chiavi considerate uguali se nome e posizione sono uguali. Da C#/Java Sono abituato a dover eseguire l'override e fornire un metodo equals and hashcode, e prometto di non mutare nulla da cui dipende l'hashcode.

Cosa devo fare in Python per realizzare questo? Dovrei anche?

(In un caso semplice, come qui, forse sarebbe meglio mettere solo un (nome, posizione) tupla come chiave - ma prendere in considerazione vorrei la chiave per essere un oggetto)

+0

Cosa c'è di sbagliato con l'utilizzo l'hash? –

+1

Probabilmente perché vuole due 'MyThing', se hanno lo stesso' nome' e 'posizione', per indicizzare il dizionario per restituire lo stesso valore, anche se sono stati creati separatamente come due diversi" oggetti ". – Santa

+1

"Forse sarebbe meglio mettere semplicemente una tupla (nome, posizione) come chiave - ma considera che vorrei che la chiave fosse un oggetto)" Vuoi dire: un oggetto NON-COMPOSITO? – eyquem

risposta

168

è necessario aggiungere 2 methods, nota __hash__ e __eq__:

class MyThing: 
    def __init__(self,name,location,length): 
     self.name = name 
     self.location = location 
     self.length = length 

    def __hash__(self): 
     return hash((self.name, self.location)) 

    def __eq__(self, other): 
     return (self.name, self.location) == (other.name, other.location) 

    def __ne__(self, other): 
     # Not strictly necessary, but to avoid having both x==y and x!=y 
     # True at the same time 
     return not(self == other) 

Il Python dict documentation definisce questi requisiti oggetti chiave, vale a dire devono essere hashable.

+15

'hash (self.name)' sembra più bello di 'self.name .__ hash __()', e se lo fai e puoi fare 'hash ((x, y))' per evitare XORing te stesso. –

+3

Come nota aggiuntiva, ho appena scoperto che chiamare 'x .__ hash __()' mi piace anche * sbagliato *, perché _can_ produce _incorrect_ results: http://pastebin.com/C9fSH7eF –

+0

@Rosh Oxymoron: grazie per il commento. Durante la scrittura usavo esplicitamente 'and' per' __eq__', ma poi ho pensato "perché non usare le tuple?" perché lo faccio spesso (penso sia più leggibile). Per qualche strano motivo i miei occhi non sono tornati alla domanda su '__hash__' comunque. – 6502

18

È sostituisci __hash__ se vuoi hash-semantics speciali e __cmp__ o __eq__ per rendere la tua classe utilizzabile come chiave. Gli oggetti che confrontano lo stesso devono avere lo stesso valore di hash.

Python si aspetta __hash__ di restituire un numero intero, tornando Banana() non è raccomandato :)

classi

utente definito hanno __hash__ di default che chiama id(self), come avrete notato.

C'è qualche consiglio in più dalla documentation:.

classi che ereditano un metodo __hash__() da una classe padre, ma cambiano il significato di __cmp__() o __eq__() tale che il valore di hash restituito è no più appropriato (ad esempio per passando a un concetto basato sul valore di uguaglianza anziché all'uguaglianza basata sull'identità predefinita ) è possibile che sia contrassegnato esplicitamente come inammissibile impostando __hash__ = None nella definizione della classe. In questo modo significa che non solo le istanze di classe alzare un adeguato TypeError quando un programma tenta di recuperare il loro valore di hash, ma saranno anche correttamente identificate come calcolo dell'hash al momento del check isinstance(obj, collections.Hashable) (a differenza di classi che definiscono il proprio __hash__() per sollevare esplicitamente l'errore TypeError).

+2

L'hash da solo non è sufficiente, inoltre è necessario eseguire l'override di '__eq__' o' __cmp__'. –

+0

@Oben Sonne: '__cmp__' è dato da Python se è una classe definita dall'utente, ma probabilmente vorrai sovrascriverli comunque per adattarsi alla nuova semantica. – Skurmedel

+1

@Skurmedel: Sì, ma sebbene tu possa chiamare 'cmp' e usare' = 'nelle classi utente che non sovrascrivono questi metodi, uno di essi deve essere implementato per soddisfare il requisito del questionario che le istanze con nome e posizione simili abbiano lo stesso chiave del dizionario. –

28

Un'alternativa in Python 2.6 o superiore è di usare collections.namedtuple() - consente di risparmiare iscritto eventuali metodi speciali:

from collections import namedtuple 
MyThingBase = namedtuple("MyThingBase", ["name", "location"]) 
class MyThing(MyThingBase): 
    def __new__(cls, name, location, length): 
     obj = MyThingBase.__new__(cls, name, location) 
     obj.length = length 
     return obj 

a = MyThing("a", "here", 10) 
b = MyThing("a", "here", 20) 
c = MyThing("c", "there", 10) 
a == b 
# True 
hash(a) == hash(b) 
# True 
a == c 
# False 
Problemi correlati