¿Qué hace que una clase definida por el usuario sea inutilizable?
Los documentos dicen que una clase es hashable siempre y cuando defina el método __hash__
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 inhashable?
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, violaré este requisito en las funciones hash:
La única propiedad requerida es que los objetos que comparan equal tienen el mismo valor hash
Los documentos advierten que un objeto hashable no debería ser modificado durante su vida útil, y por supuesto no modifico instancias de X
después de la creación. Por supuesto, el intérprete no comprobará eso de todos modos.
4 answers
Simplemente establecer el método __hash__
al de la clase tuple
no es suficiente. En realidad no le has dicho cómo hacer hash de manera diferente. las tuplas son hashable porque son inmutables. Si realmente quieres hacer que tu ejemplo específico funcione, podría ser así:
class X2(list):
def __hash__(self):
return hash(tuple(self))
En este caso, en realidad está definiendo cómo hash su subclase de lista personalizada. Solo tienes que definir exactamente cómo puede generar un hash. Puedes hacer hash en lo que quieras, en lugar de usar el hash de la tupla método:
def __hash__(self):
return hash("foobar"*len(self))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-20 23:14:48
De los documentos Python3:
Si una clase no define un método __eq__() no debe definir un __hash__() operación; si se define __eq__ (), pero no __hash__(), sus instancias no podrán ser utilizados como elementos de hashable colecciones. Si una clase define objetos mutables e implementa un __eq__() método, no debe implementar __hash__(), ya que la ejecución de hashable colecciones requiere que un hash de la clave el valor es inmutable (si el valor hash del objeto cambios, será en el cubo de hachís equivocado).
Ref: objeto.__ 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()))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-03 14:29:58
Lo que podría y debería hacer, basado en su otra pregunta, es: no subclases nada, solo encapsula 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
()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-20 23:33:33
Si no modifica las instancias de X
después de la creación, ¿por qué no está subclasificando la tupla?
Pero señalaré que esto en realidad 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([[]])
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 el id del objeto como un hash. Cuando realmente llamas __hash__
todavía obtienes 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 los internos de python, para algunos razón, están detectando que X
tiene un __hash__
y un método __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 es el enfoque más obvio.
def __hash__(self):
return hash(tuple(self))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-20 23:24:40