Короткий ответ
Python требует, чтобы равные по __eq__ объекты имели одинаковый хеш (__hash__) для корректной работы в set и как ключи dict; если переопределить __eq__ и не переопределить __hash__, Python по умолчанию делает объект нехешируемым (__hash__ становится None), и попытка положить его в set/словарь приведёт к TypeError.
Как это работает подробнее
Контракт хеш-таблиц: если a == b, то должно быть hash(a) == hash(b) — иначе два "равных" объекта могут попасть в разные корзины хеш-таблицы, и словарь/множество перестанет находить уже добавленный эквивалентный элемент. Как только класс явно определяет __eq__, Python автоматически отключает унаследованный __hash__ (устанавливает его в None), чтобы не нарушить этот контракт случайно, — хеш нужно либо переопределить согласованно с __eq__, либо явно оставить класс нехешируемым.
Пример кода
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y)) # согласован с полями из __eq__
p1, p2 = Point(1, 2), Point(1, 2)
print(p1 == p2) # True
print({p1, p2}) # {<Point ...>} — один элемент, хеши совпали
print(len({p1, p2})) # 1Что использовать на практике
- Практика. Хеш обычно строят из тех же полей, которые участвуют в сравнении равенства, оборачивая их в
hash((self.field1, self.field2)). Для изменяемых объектов (чьи поля, участвующие в равенстве, могут меняться после создания) переопределять__hash__обычно не стоит — такие объекты логично оставить нехешируемыми, чтобы не ломать инварианты множеств/словарей при изменении объекта после вставки.
Подводные камни
- Риски. Переопределяют только
__eq__для сравнения "по значению" (например, по id сущности) и удивляютсяTypeError: unhashable type, пытаясь положить объекты в set. Другая ошибка — переопределить__hash__на основе изменяемых полей и затем мутировать объект после того, как он уже лежит в set/dict, из-за чего объект "теряется" (его больше невозможно найти по новому значению хеша в старой корзине).