← Назад к вопросам

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

1.0 Junior🔥 161 комментариев
#Python

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Да, абсолютно можно написать хэшируемый кастомный класс на Python. Хэшируемость — это способность объекта быть ключом словаря или элементом множества. Для этого нужно понимать несколько ключевых моментов.

Как работает хэширование в Python

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

Правило золотое

Если два объекта равны (a == b), они должны иметь одинаковый хэш (hash(a) == hash(b)). Это критически важно для корректной работы словарей и множеств.

Пример хэшируемого класса

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return self.x == other.x and self.y == other.y
    
    def __hash__(self):
        return hash((self.x, self.y))
    
    def __repr__(self):
        return f"Point({self.x}, {self.y})"

# Использование
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2)  # True
print(hash(p1) == hash(p2))  # True — отлично!

# Можно использовать в словаре
points = {p1: "origin", p3: "corner"}
print(points[p2])  # "origin"

# Можно использовать во множествах
point_set = {p1, p2, p3}
print(len(point_set))  # 2 — p1 и p2 считаются одним элементом

Важные требования

1. Неизменяемость — хэшируемые объекты не должны менять своё состояние. Если вы измените p1.x, его хэш изменится, и объект потеряется в словаре.

2. Использование неизменяемых атрибутов — для __hash__() используйте только те атрибуты, которые не меняются:

class User:
    def __init__(self, user_id, name):
        self.user_id = user_id  # неизменяемый идентификатор
        self.name = name
    
    def __eq__(self, other):
        return self.user_id == other.user_id
    
    def __hash__(self):
        return hash(self.user_id)

3. Если переопределяешь __eq__, обязательно переопредели __hash__ или явно установи __hash__ = None, чтобы класс стал нехэшируемым.

Классы с хэшем по умолчанию

Встроенные типы (строки, числа, кортежи) хэшируемы, потому что они неизменяемы и имеют корректно реализованные __hash__() и __eq__().

Антипаттерн: нарушение инварианта

# ❌ ПЛОХО
class BadPoint:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    # __hash__ НЕ определён!
    # Python установит __hash__ = None

p1 = BadPoint(1, 2)
points_set = {p1}  # TypeError: unhashable type

Хэширование с @dataclass

Начиная с Python 3.7, можно использовать @dataclass с параметром frozen=True:

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

p1 = Point(1, 2)
points = {p1}  # Автоматически хэшируемо!
p1.x = 3  # AttributeError — защита от изменений

Практический совет для Data Science

Делайте объект хэшируемым только если:

  • Он логически неизменяем
  • Вы планируете использовать его как ключ словаря или в множестве
  • Имеет смысл сравнивать объекты по значению

Это особенно полезно при кэшировании результатов функций, использовании объектов как ключей в словарях параметров моделей или при дедупликации уникальных объектов в датасетах.