Типы, определяющие `__eq__`, нехешируемы?

У меня возникла странная ошибка при переносе функции в вилку Python 3.1 моей программы. Я сузил его до следующей гипотезы:

В отличие от Python 2.x, в Python 3.x, если объект имеет метод __eq__, он автоматически становится нехешируемым.

Это правда?

Вот что происходит в Python 3.1:

>>> class O(object):
...     def __eq__(self, other):
...         return 'whatever'
...
>>> o = O()
>>> d = {o: 0}
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    d = {o: 0}
TypeError: unhashable type: 'O'

Следующий вопрос: как мне решить мою личную проблему? У меня есть объект ChangeTracker, который хранит WeakKeyDictionary, который указывает на несколько объектов, давая каждому значение их дампа рассола в определенный момент времени в прошлом. Всякий раз, когда существующий объект регистрируется, трекер изменений сообщает, идентичен ли его новый рассол со старым, тем самым сообщая, изменился ли объект за это время. Проблема в том, что теперь я даже не могу проверить, находится ли данный объект в библиотеке, потому что это вызывает исключение о том, что объект не может быть хэширован. (Потому что у него есть метод __eq__.) Как я могу обойти это?


person Ram Rachum    schedule 22.10.2009    source источник
comment
Что произойдет, если вы предоставите __hash__ метод?   -  person ndim    schedule 22.10.2009
comment
Я удивлен, что никто не обсуждает, почему это не проблема для Python 2.   -  person nehem    schedule 20.08.2019


Ответы (4)


Да, если вы определите __eq__, значение по умолчанию __hash__ (а именно хеширование адреса объекта в памяти) исчезнет. Это важно, потому что хеширование должно согласовываться с равенством: одинаковые объекты должны хешироваться одинаково.

Решение простое: просто определите __hash__ вместе с определением __eq__.

person Martin v. Löwis    schedule 22.10.2009
comment
Позвольте мне добавить для потомков: я определил __hash__ как return id(self). - person Ram Rachum; 22.10.2009
comment
@ cool-RR: Использование id(self) в __hash__(), безусловно, неверно, если вы не определили __eq__() для сравнения по идентичности объекта (что кажется бессмысленным). Как насчет обновления вашего вопроса реальной __eq__() реализацией, чтобы мы могли предложить __hash__() его дополнить? - person Denis Otkidach; 23.10.2009
comment
Я тоже пытался решить эту проблему; Я придумал это: def __eq__(): return self.__dict__ == other.__dict__ и def __hash__(): return hash(self.__dict__.values()) - person Turtles Are Cute; 25.12.2015
comment
Это не лучший ответ - уходит не только хэш по умолчанию - это любой унаследованный хеш. - person Eric; 22.10.2016

Этот абзац из http://docs.python.org/3.1/reference/ datamodel.html # object. хэш

Если класс, который переопределяет __eq__(), должен сохранить реализацию __hash__() из родительского класса, интерпретатору необходимо сообщить об этом явно, установив __hash__ = <ParentClass>.__hash__. В противном случае наследование __hash__() будет заблокировано, как если бы для __hash__ было явно установлено значение None.

person newacct    schedule 22.10.2009
comment
Так это будет def __hash__ (self): return <ParentClass>.__hash__? Мне не удается заставить его работать, и я не нашел ни одного примера в Интернете. - person Jesus Martinez Garcia; 22.06.2021
comment
@JesusMartinezGarcia: вы должны написать строку __hash__ = <ParentClass>.__hash__ внутри определения класса (вместо определения метода с помощью def), или вы можете сделать def __hash__(self): return <ParentClass>.__hash__(self) - person newacct; 22.06.2021
comment
Я пробовал обе эти вещи и получаю сообщение об ошибке. Первое предложение: __hash__ = <ParentClass>.__hash__ ^ SyntaxError: invalid syntax Второе предложение: File "<string>", line 24 return <ParentClass>.__hash__(self) ^ SyntaxError: invalid syntax (Мне не удается разместить разрывы в нужном месте, но, по сути, стрелка указывает на '‹' перед ParentClass) - person Jesus Martinez Garcia; 22.06.2021
comment
@JesusMartinezGarcia: это не означает буквально поместить <ParentClass>, но означает поместить туда имя родительского класса - person newacct; 22.06.2021
comment
О, Боже. Я не осознавал этого, что теперь кажется очевидным. Спасибо, что указали на это. Я думал, что ‹ParentClass› автоматически вызовет родительский класс, но теперь я понимаю, что это невозможно, поскольку self даже не указан. Спасибо за это. - person Jesus Martinez Garcia; 24.06.2021

См. Руководство по Python 3 на странице object.__hash__:

Если класс не определяет __eq__() метод, он также не должен определять __hash__() операцию; если он определяет __eq__(), но не __hash__(), его экземпляры не будут использоваться в качестве элементов в хэшируемых коллекциях.

Акцент мой.

Если вы хотите быть ленивым, похоже, вы можете просто определить __hash__(self) для возврата id(self):

Пользовательские классы по умолчанию имеют методы __eq__() и __hash__(); с ними все объекты сравниваются неравно (кроме самих себя), и x.__hash__() возвращает id(x).

person Mark Rushakoff    schedule 22.10.2009
comment
Конечно, единственная причина перегрузить оператор равенства состоит в том, что два разных объекта иногда могут сравнивать равные значения (если нет, то не беспокойтесь о его перегрузке). В этом случае хеш-функция return id(self) не работает (одинаковые объекты должны хешировать одинаково, см. Ответ Мартина). Хэш-функция "Мне все равно" будет return 1. Это просто и удовлетворяет всем условиям (это просто очень неэффективно!) - person Scott Griffiths; 23.10.2009

Я не эксперт по питону, но разве не было бы смысла в том, что, когда вы определяете eq-метод, вы также должны определять хэш-метод (который вычисляет хеш-значение для объекта) В противном случае механизм хеширования не будет знать, попал ли он в тот же объект или другой объект с тем же хеш-значением. На самом деле, все наоборот, это, вероятно, в конечном итоге приведет к вычислению разных хеш-значений для объектов, которые ваш метод __eq__ считает равными.

Я понятия не имею, что называется эта хеш-функция, __hash__ возможно? :)

person falstro    schedule 22.10.2009
comment
К вашему сведению: я нарисовал это некоторое время назад (mindmeister.com/10510492/python-underscore) : карта разума всех методов подчеркивания в python. - person jldupont; 22.10.2009
comment
На самом деле все наоборот. Если два объекта имеют одинаковое хеш-значение, но не равны - это нормально. Механизм хеширования (то есть словарь) сначала проверяет хеш-код, а на том же хеш-коде также сравнивает на равенство. Настоящая проблема возникает наоборот: объекты хешируются по-разному, но все равно сравниваются. Словарь должен их найти, но не может (или вы можете получить повторяющиеся ключи в словаре). - person Martin v. Löwis; 22.10.2009
comment
@Martin v. Löwis: Я понял это и добавил это в конце, или есть небольшая разница в том, что я сказал? - person falstro; 23.10.2009