python class python-3.x user-defined-types hashable

python - ¿Qué hace que una clase definida por el usuario sea inestable?



class python-3.x (4)

Los docs dicen que una clase es hashable siempre que defina el método __eq__ y el método __eq__ . Sin embargo:

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 sea ​​unashashable?

Tenga en cuenta que debo tener listas idénticas (en términos de igualdad regular) para tener el mismo valor; De lo contrario, violaré este requisito en las funciones hash:

La única propiedad requerida es que los objetos que comparan igual tengan el mismo valor hash

Los documentos advierten que un objeto hashable no debe modificarse 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.


Basta con establecer el método __hash__ en el de la clase tuple no es suficiente. En realidad no le has dicho cómo hash de manera diferente. Las tuplas son hashables porque son inmutables. Si realmente quisieras que tu ejemplo de trabajo funcione, podría ser así:

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

En este caso, en realidad está definiendo cómo codificar su subclase de lista personalizada. Solo tienes que definir exactamente cómo puede generar un hash. Puede hacer hash en lo que quiera, en lugar de usar el método de hash de la tupla:

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


De los documentos de Python3:

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

Ref: object.__hash__(self)

Código de muestra:

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()))


Lo que podría y debería hacer, basado en su otra pregunta, es: no haga una subclase de nada, simplemente encapsule una tupla. Está perfectamente bien hacerlo en el inicio.

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}

cuyos rendimientos:

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


Si no modificas las instancias de X después de la creación, ¿por qué no estás subclasificando tuple?

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

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

Dudo en decir "funciona" porque esto no hace lo que crees que hace.

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

Solo está usando la identificación del objeto como un hash. Cuando en realidad llamas __hash__ aún obtienes un error; igualmente 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

Supongo que los internos de python, por alguna razón, están detectando que X tiene un __hash__ y un __eq__ , pero no los están llamando.

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

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