Какие dunder-методы отвечают за хэширование объектов в Python?

Ответ

За хэширование объектов в 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 прямо в ебало, как от хорошего друга. Так что не выёбывайся, делай оба.