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

«Какие dunder-методы отвечают за хэширование объектов в Python?» — вопрос из категории Python, который задают на 10% собеседований 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.