2012-04-20 15 views
23

El docs dice que una clase es manejable siempre que defina el método __hash__ y el método __eq__. Sin embargo:¿Qué hace que una clase definida por el usuario sea indestructible?

class X(list): 
    # read-only interface of `tuple` and `list` should be the same, so reuse tuple.__hash__ 
    __hash__ = tuple.__hash__ 

x1 = X() 
s = {x1} # TypeError: unhashable type: 'X' 

¿Qué hace que X unhashable?

Tenga en cuenta que debo tener listas idénticas (en términos de igualdad regular) para ser hash con el mismo valor; de lo contrario, lo haré violate this requirement en funciones hash:

La única propiedad requerida es que los objetos que se comparan tienen la misma el mismo valor hash

Los documentos advierten de que un objeto hashable no debe ser modificado durante su vida útil y, por supuesto, no modifico las instancias de X después de la creación. Por supuesto, el intérprete no lo comprobará de todos modos.

+2

Sí, las interfaces de sólo lectura son los mismos, pero ¿por qué se puede esperar tupla .__ hash__ a sólo utilizan las interfaces externas de su propio ¿clase? Especialmente cuando está escrito en C. Usar las interfaces externas sería mucho más lento. No se puede esperar razonablemente que un método de la clase A funcione para la clase B a menos que la clase B esté subclasificada de la clase A. ¿Incluso trataste de llamar a x1 .__ hash __() para ver si funcionaba? –

+0

@LennartRegebro Sí, estoy de acuerdo ... Vea mi último comentario en http: //stackoverflow.com/a/10254636/336527 ... Acabo de tener una congelación cerebral. – max

Respuesta

15

Simplemente configurando el método __hash__ al de la clase tuple no es suficiente. En realidad, no le has dicho cómo hash de manera diferente. las tuplas son lavables porque son inmutables. Si realmente quería hacer que el trabajo ejemplo específico, podría ser así:

class X2(list): 
    def __hash__(self): 
     return hash(tuple(self)) 

En este caso, en realidad se está definiendo la forma en Hash su lista personalizada subclase. Solo tienes que definir exactamente cómo puede generar un hash. Puede hachís en lo que quieras, en lugar de utilizar el método de codificación de la tupla:

def __hash__(self): 
    return hash("foobar"*len(self)) 
+0

¿Pero no es 'tuple .__ hash__' una función que toma una tupla y devuelve un número? ¿Cómo funciona esa función "darse cuenta" de que mi objeto es en realidad una "lista" en lugar de una 'tupla' - la API de lectura para los dos tipos es idéntica. – max

+0

@max: 'tuple .__ hash__' es un método enlazado de la clase tupla. No está cambiando lo que sea que su implementación esté haciendo dentro de ese método para hash. Define el tuyo – jdi

+0

'hash ((1,2,3))' es lo mismo que '(1,2,3) .__ hash__'; eso es lo mismo que 'tuple .__ hash __ ((1,2,3))', ¿verdad? Entonces 'tuple .__ hash__' depende de la API no pública de la clase' tuple', y entonces se descompone con un mensaje de error confuso cuando pasa una instancia de una clase diferente que coincide con la API pública 'tuple' Supongo que lo explica ... pero un poco inesperado. – max

3

Si usted no modifica las instancias de X después de la creación, ¿por qué no te subclassing par de valores?

Pero señalaré que esto realmente no arroja un error, al menos en Python 2.6.

>>> class X(list): 
...  __hash__ = tuple.__hash__ 
...  __eq__ = tuple.__eq__ 
... 
>>> x = X() 
>>> s = set((x,)) 
>>> s 
set([[]]) 

Vacilo en decir "funciona" porque esto no hace lo que usted piensa que hace.

>>> a = X() 
>>> b = X((5,)) 
>>> hash(a) 
4299954584 
>>> hash(b) 
4299954672 
>>> id(a) 
4299954584 
>>> id(b) 
4299954672 

Solo está utilizando la identificación del objeto como hash. Cuando realmente llama al __hash__, sigue recibiendo un error; del mismo modo para __eq__.

>>> a.__hash__() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: descriptor '__hash__' for 'tuple' objects doesn't apply to 'X' object 
>>> X().__eq__(X()) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: descriptor '__eq__' for 'tuple' objects doesn't apply to 'X' object 

deduzco que las partes internas de pitón, por alguna razón, están detectando que X tiene un __hash__ y un método __eq__, pero no están llamando a ellos.

La moraleja de todo esto es: simplemente escriba una función hash real. Como este es un objeto de secuencia, convertirlo en una tupla y hash es el enfoque más obvio.

def __hash__(self): 
    return hash(tuple(self)) 
+0

Lo siento mucho, esta pregunta se saca fuera del contexto de otra. Simplemente estaba confundido acerca de este comportamiento en particular. La razón por la que la lista de subclase es un poco complicada (ver discusión en comentarios a [esta pregunta] (http://stackoverflow.com/questions/10253783/making-a-list-subclass-hashable)). – max

+0

El código no funciona para mí en ActiveState Python 3.2. Tal vez el comportamiento cambió recientemente? – max

+0

Estoy usando Python 2.6. En cualquier caso, no desea este comportamiento, porque el uso de 'id's como claves no es realmente una buena idea. Mejor simplemente convertir a tupla y hash eso. Y en realidad, lo siento; esto fue solo un enfoque bastante desconcertante para el problema para mí. – senderle

5

Lo que podría y debería hacer, en base a su otra pregunta, es: no subclase nada, sólo encapsular una tupla. Está perfectamente bien hacerlo en el init.

class X(object): 
    def __init__(self, *args): 
     self.tpl = args 
    def __hash__(self): 
     return hash(self.tpl) 
    def __eq__(self, other): 
     return self.tpl == other 
    def __repr__(self): 
     return repr(self.tpl) 

x1 = X() 
s = {x1} 

que produce:

>>> s 
set([()]) 
>>> x1 
() 
+0

Tiene razón, para muchos casos de uso esta es la solución más simple y más limpia; +1 – senderle

4

A partir de los documentos python3:

Si una clase no define un método __eq __() no debe definir una operación de __hash __() ya sea ; si define __eq __() pero no __hash __(), sus instancias no serán utilizables como elementos en las colecciones hashable. Si una clase define objetos mutables e implementa un método __eq __(), no debería implementar __hash __(), ya que la implementación de colecciones hashable requiere que el valor hash de una clave sea inmutable (si el valor hash del objeto cambia, estará en el cubo de hash incorrecto).

Ref: object.__hash__(self)

Código de ejemplo:

class Hashable: 
    pass 

class Unhashable: 
    def __eq__(self, other): 
     return (self == other) 

class HashableAgain: 
    def __eq__(self, other): 
     return (self == other) 

    def __hash__(self): 
     return id(self) 

def main(): 
    # OK 
    print(hash(Hashable())) 
    # Throws: TypeError("unhashable type: 'X'",) 
    print(hash(Unhashable())) 
    # OK 
    print(hash(HashableAgain())) 
+0

¿'' __hash__' necesita ser único? Supongamos que quiere comparar las instancias de 'HashableAgain' en función de los criterios que ha definido en' __eq__' ¿puede devolver un entero constante en '__hash__'? (Realmente no entiendo cómo hash) se usa para decidir la membresía de un objeto en un conjunto. –

+0

@MinhTran: en general, hash no es único, sino _relativamente_ único. Se usa para almacenar valores en un mapa. Si usa un valor constante para hash, todos los valores aparecerán en el mismo cubo, por lo que el rendimiento será horrible ... ¡pero aún así debería funcionar! – kevinarpe

Cuestiones relacionadas