2011-02-04 13 views
146

¿Qué debo hacer para utilizar mis objetos de un tipo personalizado como claves en un diccionario de Python (donde no deseo que el "identificador de objeto" actúe como clave), p.Objeto de tipo personalizado como clave de diccionario

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

Me gustaría utilizar MyThing como claves que se consideran iguales si el nombre y la ubicación son los mismos. Desde C#/Java, estoy acostumbrado a tener que anular y proporcionar un método equal y hashcode, y prometo no mutar nada de lo que dependa el código hash.

¿Qué debo hacer en Python para lograr esto? Debería incluso?

(En un caso sencillo, como aquí, tal vez sería mejor simplemente colocar un (nombre, ubicación) tupla como clave - pero tenga en cuenta que me gustaría ser la clave para un objeto)

+0

¿Qué hay de malo en usar el hash? –

+1

Probablemente porque quiere dos 'MyThing', si tienen el mismo' nombre' y 'ubicación', para indexar el diccionario para devolver el mismo valor, incluso si se crearon por separado como dos" objetos "diferentes. – Santa

+1

"quizás sería mejor simplemente colocar una tupla (nombre, ubicación) como clave, pero considere que quisiera que la clave sea un objeto)" ¿Quiere decir: un objeto NO COMPUESTO? – eyquem

Respuesta

168

es necesario agregar 2 methods, tenga en cuenta __hash__ y __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) 

El pitón dict documentation define estos requisitos sobre los objetos clave, es decir, deben ser hashable.

+15

'hash (self.name)' se ve mejor que 'self.name .__ hash __()', y si lo hace y puede hacer 'hash ((x, y))' para evitar XORing usted mismo. –

+3

Como nota adicional, descubrí que llamar 'x .__ hash __()' de esa manera también es * wrong *, porque _can_ produce _incorrect_ results: http://pastebin.com/C9fSH7eF –

+0

@Rosh Oxymoron: gracias por el comentario. Al escribir, estaba usando 'y' explícito para '__eq__' pero luego pensé" ¿por qué no usar tuplas? " porque a menudo hago eso de todos modos (creo que es más legible). Por alguna extraña razón, mis ojos no volvieron a preguntar sobre '__hash__' sin embargo. – 6502

18

Usted anula __hash__ si quieres una semántica hash especial, y __cmp__ o __eq__ para hacer que tu clase se pueda utilizar como clave. Los objetos que se comparan por igual deben tener el mismo valor hash.

Python espera __hash__ para devolver un número entero, volviendo Banana() no es recomendable :)

clases

usuario definido __hash__ tienen por defecto que llama id(self), como usted señaló.

Hay algunos consejos adicionales de la documentation:.

clases que heredan un método __hash__() de una clase padre, pero cambian el significado de __cmp__() o __eq__() tal que el valor hash devuelto es sin más apropiado (por ejemplo, por cambiando a un concepto basado en el valor de igualdad en lugar de la predeterminada igualdad basada en la identidad) puede señalarse explícitamente como unhashable configurando __hash__ = None en la definición de clase. Si lo hace, significa que no sólo los casos de la clase plantear una TypeError apropiado cuando un programa intenta recuperar su valor hash, pero también estarán correctamente identificados como unhashable al comprobar isinstance(obj, collections.Hashable) (a diferencia de clases que definen su propio __hash__() para generar explícitamente TypeError).

+2

El hash solo no es suficiente, además, o bien debe anular '__eq__' o' __cmp__'. –

+0

@Oben Sonne: Python le otorga '' __cmp__' si es una clase definida por el usuario, pero es probable que desee sobrescribirlos de todos modos para adaptarse a la nueva semántica. – Skurmedel

+1

@Skurmedel: Sí, pero aunque puede llamar a 'cmp' y usar' = 'en las clases de usuarios que no anulan estos métodos, uno de ellos debe implementarse para cumplir con el requisito del interrogador de que las instancias con nombre y ubicación similares tengan el mismo clave del diccionario –

28

Una alternativa en Python 2.6 o superior es utilizar collections.namedtuple() - que le ahorra escribir cualquiera de los procedimientos especiales:

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 
Cuestiones relacionadas