Ответ
За хэширование объектов в Python отвечают два dunder-метода: __hash__ и __eq__. Они должны быть реализованы вместе, чтобы объект мог корректно использоваться в хэшируемых коллекциях, таких как словари (dict) и множества (set).
__hash__(self): Должен возвращать целочисленное значение (хэш). Объекты с одинаковым содержимым, которые считаются равными, должны иметь одинаковый хэш.__eq__(self, other): Определяет логику сравнения двух объектов на равенство (==).
Золотое правило хэширования: если a == b истинно, то hash(a) == hash(b) также должно быть истинно.
Почему они нужны вместе?
Словари и множества используют хэш для быстрого поиска "корзины" (bucket), где может находиться элемент. Если в одной корзине оказывается несколько элементов (коллизия), Python использует __eq__ для поиска точного совпадения среди них.
Пример реализации
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# Метод для сравнения объектов
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
# Метод для вычисления хэша
# Хэшируем кортеж атрибутов, так как кортежи хэшируемы по умолчанию
def __hash__(self):
return hash((self.x, self.y))
# Теперь объекты класса Point можно использовать в множествах и как ключи словаря
p1 = Point(1, 2)
p2 = Point(1, 2) # Такой же объект
p3 = Point(3, 4)
points_set = {p1, p2, p3}
# В множестве останутся только уникальные объекты
# {<Point object at ...>, <Point object at ...>}
print(len(points_set)) # Вывод: 2
# p1 и p2 считаются равными
print(p1 == p2) # Вывод: True
# и имеют одинаковый хэш
print(hash(p1) == hash(p2)) # Вывод: True
Если определить только __eq__, класс по умолчанию становится нехэшируемым, и попытка добавить его экземпляр в set вызовет TypeError.
Ответ 18+ 🔞
Слушай, а вот этот ваш хэширование объектов в Python — это вообще пиздец какой-то важный момент, если ты хочешь, чтобы твои кастомные штуки в словарях и множествах работали, а не вываливались с ошибками, как говно из проруби.
Так вот, есть два этих, блядь, магических метода: __hash__ и __eq__. Их надо реализовывать вместе, ёпта, как сиамских близнецов. Один без другого — это как хуй без яиц, бесполезно.
__hash__(self): Этот товарищ должен вернуть целое число, этот самый хэш. И главное правило, блядь, железное: если два объекта считаются равными (по__eq__), то и хэши у них должны быть одинаковые, иначе пиши пропало.__eq__(self, other): А этот уже решает, равны ли два объекта через==.
Суть в чём, ёпта: Коллекции вроде dict и set сначала смотрят на хэш, чтобы быстро найти нужную "корзину". А если в этой корзине уже сидит кто-то (коллизия, мать её), вот тогда уже дергается __eq__, чтобы понять — это точно тот самый объект или просто случайный однофамилец.
Вот смотри, как это выглядит на практике, чтобы не быть, прости господи, распиздяем:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# Метод для сравнения объектов
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
# Метод для вычисления хэша
# Хэшируем кортеж атрибутов, так как кортежи хэшируемы по умолчанию
def __hash__(self):
return hash((self.x, self.y))
# Теперь объекты класса Point можно использовать в множествах и как ключи словаря
p1 = Point(1, 2)
p2 = Point(1, 2) # Такой же объект
p3 = Point(3, 4)
points_set = {p1, p2, p3}
# В множестве останутся только уникальные объекты
# {<Point object at ...>, <Point object at ...>}
print(len(points_set)) # Вывод: 2
# p1 и p2 считаются равными
print(p1 == p2) # Вывод: True
# и имеют одинаковый хэш
print(hash(p1) == hash(p2)) # Вывод: True
А если ты, такой умный, реализуешь только __eq__, то класс твой станет нехэшируемым. И попробуй ты его в set засунуть — получишь TypeError прямо в ебало, как от хорошего друга. Так что не выёбывайся, делай оба.