Что делает определяемый пользователем класс недоступным для прослушивания?
Документы говорят, что класс является хэшируемым, пока он определяет __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
unhashable?
Обратите внимание, что у меня должны быть одинаковые списки (с точки зрения регулярного равенства), которые должны быть хэшированы до одного и того же значения; в противном случае я нарушу это требование к хеш-функциям:
Единственным обязательным свойством является то, что объекты, которые сравниваются равными, имеют одинаковое значение хеш
Документы предупреждают, что хешируемый объект не должен изменяться во время его жизни, и, конечно, я не изменяю экземпляры X
после создания. Конечно, переводчик все равно не проверит это.
5 ответов
Просто настройка __hash__
метод к тому из tuple
класс не достаточно. Вы на самом деле не сказали, как хэшировать по-другому. кортежи являются хэшируемыми, потому что они неизменны. Если вы действительно хотите, чтобы ваш конкретный пример работал, это может выглядеть так:
class X2(list):
def __hash__(self):
return hash(tuple(self))
В этом случае вы фактически определяете, как хэшировать свой подкласс пользовательского списка. Вам просто нужно точно определить, как он может генерировать хеш. Вы можете хэшировать все, что захотите, в отличие от использования метода хэширования кортежа:
def __hash__(self):
return hash("foobar"*len(self))
Из документов Python3:
Если класс не определяет метод __eq__(), он также не должен определять операцию __hash __ (); если он определяет __eq__(), но не __hash__(), его экземпляры не будут использоваться в качестве элементов в хешируемых коллекциях. Если класс определяет изменяемые объекты и реализует метод __eq__(), он не должен реализовывать __hash__(), поскольку реализация хэшируемых коллекций требует, чтобы хеш-значение ключа было неизменным (если хеш-значение объекта изменяется, оно будет неверным хэш ведро).
Ссылка: объект.__ хеш __ (самостоятельно)
Образец кода:
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()))
Что вы могли и должны сделать, основываясь на другом вопросе: не делайте ничего на подклассы, просто инкапсулируйте кортеж. Это прекрасно в 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
()
В дополнение к приведенным выше ответам. Для конкретного случая класса данных в python3.7+ - чтобы сделать класс данных хешируемым, вы можете использовать
@dataclass(frozen=True)
class YourClass:
pass
как украшение вместо
@dataclass
class YourClass:
pass
Если вы не измените экземпляры 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))