Что делает пользовательский класс неумелым?
docs говорят, что класс hashable, если он определяет метод __hash__
и __eq__
. Однако:
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'
Что делает X
недоступным для просмотра?
Обратите внимание, что я должен иметь одинаковые списки (с точки зрения регулярного равенства) для хэширования до одного значения; в противном случае я нарушит это требование по хэш-функциям:
Единственное требуемое свойство состоит в том, что объекты, которые сравнивают одинаковые, то же значение хеш-функции
Документы предупреждают, что хешируемый объект не должен изменяться в течение его жизненного цикла, и, конечно же, я не изменяю экземпляры X
после создания. Конечно, интерпретатор не будет проверять это в любом случае.
Ответы
Ответ 1
Просто установить метод __hash__
в класс tuple
недостаточно. Вы на самом деле не сказали, как хэш по-другому. кортежи хешируются, потому что они неизменяемы. Если вы действительно хотели создать конкретный пример работы, это может быть так:
class X2(list):
def __hash__(self):
return hash(tuple(self))
В этом случае вы фактически определяете, как хешировать ваш подклас подкласса. Вам просто нужно точно определить, как он может генерировать хэш. Вы можете хешировать все, что хотите, в отличие от использования метода хеширования кортежа:
def __hash__(self):
return hash("foobar"*len(self))
Ответ 2
Что вы можете и должны делать, исходя из вашего другого вопроса:
не подклассы, просто инкапсулируйте кортеж. Это идеально подходит для этого в 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}
который дает:
>>> s
set([()])
>>> x1
()
Ответ 3
Из документов Python3:
Если класс не определяет метод __eq __(), он не должен определять __hash __(); если он определяет __eq __(), но не __hash __(), его экземпляры не будут использоваться в качестве элементов в хешируемых коллекциях. Если класс определяет изменяемые объекты и реализует __eq __(), он не должен реализовывать __hash __(), поскольку для реализации хешируемых коллекций требуется, чтобы хэш ключей значение неизменено (если значение хеша объектов изменяется, оно будет в неправильный хэш-ведро).
Ссылка: объект.__ hash __ (self)
Пример кода:
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()))
Ответ 4
Если вы не изменяете экземпляры X
после создания, почему вы не подклассифицируете кортеж?
Но я укажу, что на самом деле это не вызывает ошибки, по крайней мере, в Python 2.6.
>>> class X(list):
... __hash__ = tuple.__hash__
... __eq__ = tuple.__eq__
...
>>> x = X()
>>> s = set((x,))
>>> s
set([[]])
Я смущаюсь сказать "работает", потому что это не делает то, что вы думаете.
>>> a = X()
>>> b = X((5,))
>>> hash(a)
4299954584
>>> hash(b)
4299954672
>>> id(a)
4299954584
>>> id(b)
4299954672
Он просто использует идентификатор объекта как хэш. Когда вы на самом деле вызываете __hash__
, вы все равно получаете ошибку; аналогично для __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
Я понимаю, что внутренности python по какой-то причине обнаруживают, что X
имеет метод __hash__
и __eq__
, но не вызывает их.
Мораль всего этого: просто напишите реальную хэш-функцию. Поскольку это объект последовательности, преобразование его в кортеж и хеширование является наиболее очевидным подходом.
def __hash__(self):
return hash(tuple(self))