Подтвердить что ты не робот

Что делает пользовательский класс неумелым?

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 после создания. Конечно, интерпретатор не будет проверять это в любом случае.

4b9b3361

Ответ 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))