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