2012-06-12 12 views
34

Aquí está mi código:¿Cómo hacer un objeto apropiadamente lavable?

class Hero: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __str__(self): 
     return self.name + str(self.age) 

    def __hash__(self): 
     print(hash(str(self))) 
     return hash(str(self)) 

heroes = set() 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 1 

heroes.add(Hero('Lara Miheenko', 17)) # gets hash -2822451113328084695 
print(len(heroes)) # gets 2 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 3! WHY? 

Por qué sucede esto?
El primer y el tercer objeto tienen el mismo contenido y el mismo hash pero len() cuenta sobre 3 objetos únicos.

+0

seguro, pero su probable que tenga '' __eq__' o __cmp__' : http://docs.python.org/glossary.html#term-hashable – nhahtdh

+2

Dejando eso de lado, esta no es la mejor función hash (porque no has hashing una cadena general, uno de los componentes de cadena tiene una entropía mucho menor porque se sabe que consta de dígitos). Para una solución trivial pero bastante efectiva, tome los valores hash de los objetos por separado y xor ellos. Para obtener más magia, agréguelos a escala mediante una constante de número primo. –

+0

@KonradRudolph: hay una suposición implícita en su comentario, específicamente que se necesita un "buen" hash para que el conjunto funcione bien. Este no es el caso con la implementación 'set' de Python; ver [este comentario de las fuentes de Python] (http://hg.python.org/cpython/file/26e2ee402a0b/Objects/dictobject.c#l113) para más explicaciones. –

Respuesta

40

También necesita definir __eq__() de forma compatible con __hash__() - de lo contrario, la igualdad se basará en la identidad del objeto.

En Python 2, se recomienda también definir __ne__ para hacer != consistente con ==. En Python 3, la implementación predeterminada __ne__ se delegará en usted al __eq__.

+6

De hecho, después de comprobar si los valores hash son iguales, el 'dict' /' set' también debe verificar la igualdad real en caso de colisión hash. –

+0

@ user2357112 Definig '__ne__' no es necesario para hacer un tipo hashable, ¿verdad? Puede ser una buena idea definirlo, ya que de lo contrario '! =' Tendrá una semántica bastante extraña, pero si todo lo que quiere hacer es usar el tipo en un conjunto o diccionario, realmente no lo necesita. –

+0

@SvenMarnach: Técnicamente, los sets y los dicts no usan '! =' En ninguna parte, pero en realidad se basa en que es una receta para errores desagradables. Incluso si los conjuntos no usan '! =', Probablemente alguien lo haga. Puedes volver a redactarlo si quieres, pero creo que la respuesta debería mencionar definitivamente '__ne__'; el espíritu de la pregunta definitivamente parece ser más "cómo hago las cosas * derecha *" que "cuál es el mínimo necesario para obtener este fragmento de código para producir el resultado esperado". – user2357112

6

The Python documentation podría ser útil:

Si una clase no define un método __cmp__() o __eq__() no debería definir una operación __hash__() tampoco;

8

Aquí es el todo el código:

class Hero: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __str__(self): 
     return self.name + str(self.age) 

    def __hash__(self): 
     print(hash(str(self))) 
     return hash(str(self)) 

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



heroes = set() 
heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 1 

heroes.add(Hero('Lara Miheenko', 17)) # gets hash -2822451113328084695 
print(len(heroes)) # gets 2 

heroes.add(Hero('Zina Portnova', 16)) # gets hash -8926039986155829407 
print(len(heroes)) # gets 2 

La función reconoce el __eq__ y como tal el len es 2.

No
Cuestiones relacionadas