Как в Python работают методы __eq__ и __hash__ для объектов, используемых в качестве ключей словаря?

«Как в Python работают методы __eq__ и __hash__ для объектов, используемых в качестве ключей словаря?» — вопрос из категории ООП, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

При использовании объекта пользовательского класса в качестве ключа словаря Python задействует два dunder-метода: __hash__ и __eq__.

Процесс работы:

  1. __hash__(self): Сначала вызывается этот метод для вычисления хэша объекта. Хэш — это целое число, которое используется для быстрого определения «корзины» (bucket) в хэш-таблице словаря, где должен храниться элемент. Это позволяет находить элементы за время, близкое к O(1).
  2. __eq__(self, other): Вызывается только в случае коллизии хэшей — когда у двух разных объектов оказывается одинаковый хэш. Этот метод выполняет точную проверку на равенство, чтобы определить, является ли ключ тем же самым.

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

Пример:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 1. Метод для проверки на равенство
    def __eq__(self, other):
        if not isinstance(other, Person):
            return NotImplemented
        return self.name == other.name and self.age == other.age

    # 2. Метод для вычисления хэша
    # Хэш должен быть одинаковым для равных объектов
    def __hash__(self):
        return hash((self.name, self.age))

p1 = Person("Alice", 30)
p2 = Person("Alice", 30) # p1 == p2, и hash(p1) == hash(p2)

data = {p1: "some_value"}

# Поиск происходит по p2, но находится значение для p1
print(data[p2]) # Вывод: some_value

Важное замечание:

  • Неизменяемость (Immutability): Ключи словаря должны быть неизменяемыми. Если хэш объекта изменится после добавления в словарь, его невозможно будет найти.
  • Согласованность: Если вы переопределяете __eq__, вы обязаны переопределить и __hash__. В противном случае объект станет "unhashable" и вызовет TypeError при попытке использовать его в качестве ключа.