Можно ли создать хэшируемый пользовательский класс в Python?

«Можно ли создать хэшируемый пользовательский класс в Python?» — вопрос из категории Python, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, для этого необходимо корректно реализовать два специальных метода: __hash__ и __eq__. Это позволит использовать экземпляры класса как ключи в словарях или элементы множеств.

Ключевые правила:

  1. Метод __eq__ определяет логику сравнения объектов на равенство.
  2. Метод __hash__ должен возвращать целочисленное значение, которое неизменно на протяжении всего времени жизни объекта.
  3. Если a == b (т.е. __eq__ возвращает True), то обязательно должно выполняться hash(a) == hash(b).

Пример реализации неизменяемого (immutable) класса:

class ImmutablePoint:
    """Пример хэшируемого класса, представляющего точку."""
    __slots__ = ('_x', '_y')  # Оптимизация памяти и запрет на добавление атрибутов

    def __init__(self, x, y):
        # Атрибуты делаем приватными и не изменяемыми после создания
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    def __hash__(self):
        # Хэш вычисляется на основе кортежа из неизменяемых полей
        return hash((self._x, self._y))

    def __eq__(self, other):
        if not isinstance(other, ImmutablePoint):
            return NotImplemented
        return (self._x, self._y) == (other._x, other._y)

# Использование
p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(1, 2)
print(p1 == p2)  # True
print(hash(p1) == hash(p2))  # True

points_set = {p1, p2}
print(points_set)  # {<__main__.ImmutablePoint object at ...>} - только один элемент
print(p1 in points_set)  # True

Важное предупреждение: Если класс является изменяемым (mutable), и его состояние влияет на __hash__, то изменение объекта после помещения в словарь или множество приведет к его "потере" в этой коллекции, так как хэш изменится. Поэтому хэшируемые классы лучше делать неизменяемыми.