← Назад к вопросам
Что должно быть у класса чтобы его объекты стали ключом словаря?
2.0 Middle🔥 131 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что нужно для использования объекта как ключа словаря
Чтобы объект класса стал ключом словаря (dict), он должен быть хешируемым. Это требует реализации двух методов: __hash__() и __eq__().
Необходимые условия
1. Метод __hash__()
Возвращает целое число, которое однозначно идентифицирует объект:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
p = Point(1, 2)
print(hash(p)) # -9223372036854775807 (числовое значение)
Хеш используется для быстрого поиска ключа в словаре (O(1) вместо O(n)).
2. Метод __eq__()
Сравнивает два объекта на равенство:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
if not isinstance(other, Point):
return False
return self.x == other.x and self.y == other.y
# Теперь можно использовать как ключ
coordinates = {}
point1 = Point(1, 2)
point2 = Point(1, 2) # Разные объекты
coordinates[point1] = "Location A"
print(coordinates[point2]) # "Location A" — они равны по значению
print(point1 == point2) # True
print(hash(point1) == hash(point2)) # True
Правило консистентности
КРИТИЧЕСКОЕ ПРАВИЛО: Если два объекта равны по __eq__(), их хеши ДОЛЖНЫ быть одинаковы.
# ПРАВИЛЬНО
if a == b:
assert hash(a) == hash(b)
# НЕПРАВИЛЬНО — ведёт к багам
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
def __hash__(self):
return id(self) # ОПАСНО! Разные объекты могут быть равны
p1 = BadPoint(1, 1)
p2 = BadPoint(1, 1)
d = {p1: "value1"}
print(p1 == p2) # True — они равны
print(hash(p1) == hash(p2)) # False — хеши разные! БАГ!
print(d[p2]) # KeyError! Не найдено, хотя должно быть
Полный рабочий пример
class User:
"""Класс для использования объектов как ключей словаря"""
def __init__(self, user_id, name):
self.user_id = user_id
self.name = name
def __hash__(self):
# Используем только уникальный идентификатор
return hash(self.user_id)
def __eq__(self, other):
if not isinstance(other, User):
return False
return self.user_id == other.user_id
def __repr__(self):
return f"User({self.user_id}, {self.name})"
# Использование
user1 = User(1, "Alice")
user2 = User(1, "Alice") # Другой объект, но с тем же ID
user3 = User(2, "Bob")
# Создать словарь с User в качестве ключей
user_info = {}
user_info[user1] = {"email": "alice@example.com", "role": "admin"}
user_info[user3] = {"email": "bob@example.com", "role": "user"}
# Поиск по другому объекту с тем же ID
print(user_info[user2]) # {"email": "alice@example.com", "role": "admin"}
# Проверка принадлежности
print(user1 in user_info) # True
print(user2 in user_info) # True — они считаются одинаковыми
print(user3 in user_info) # True
print(user1 == user2) # True
print(user1 is user2) # False — разные объекты, но одинаковые по значению
Что НЕ нужно делать
1. Не переопределяй только __eq__() без __hash__()
class BadClass:
def __eq__(self, other):
return True # Все равны
# НЕ переопределили __hash__()
# Python автоматически сделает объект нехешируемым
obj = BadClass()
d = {obj: "value"} # TypeError: unhashable type: 'BadClass'
2. Не используй изменяемые атрибуты в __hash__()
class BadUser:
def __init__(self, user_id, permissions: list):
self.user_id = user_id
self.permissions = permissions # ИЗМЕНЯЕМЫЙ
def __hash__(self):
return hash((self.user_id, tuple(self.permissions))) # Опасно
def __eq__(self, other):
return self.user_id == other.user_id
user = BadUser(1, ["read", "write"])
d = {user: "info"}
user.permissions.append("delete") # Изменили объект!
# Теперь хеш изменился, но объект всё ещё в словаре
# Это нарушает инвариант словаря — БАГ!
3. Не забывай про __repr__()
Добавь это для отладки:
class GoodClass:
def __init__(self, value):
self.value = value
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
return isinstance(other, GoodClass) and self.value == other.value
def __repr__(self):
return f"GoodClass({self.value})"
obj = GoodClass(42)
print(obj) # GoodClass(42) — намного лучше чем <GoodClass object at 0x...>
Встроенные хешируемые типы
В Python уже есть хешируемые типы:
# Все эти типы можно использовать как ключи
data = {
42: "int",
3.14: "float",
"key": "str",
(1, 2, 3): "tuple",
frozenset([1, 2]): "frozenset",
True: "bool",
None: "None"
}
# А эти — НЕЛЬЗЯ (изменяемы)
try:
data[[1, 2, 3]] = "list" # TypeError
data[{"a": 1}] = "dict" # TypeError
data[{1, 2}] = "set" # TypeError
except TypeError:
print("Нельзя использовать изменяемые типы как ключи")
Практический пример: Кэш функции
from functools import lru_cache
class Config:
def __init__(self, timeout, retry_count):
self.timeout = timeout
self.retry_count = retry_count
def __hash__(self):
return hash((self.timeout, self.retry_count))
def __eq__(self, other):
return (self.timeout == other.timeout and
self.retry_count == other.retry_count)
# Использовать Config как ключ в словаре кэша
def cache_expensive_operation(config: Config):
cache = {}
def compute():
if config in cache:
return cache[config]
result = expensive_operation(config)
cache[config] = result
return result
return compute
Итог
Чтобы использовать объект класса как ключ словаря:
- Реализовать
__hash__()— возвращает целое число - Реализовать
__eq__()— сравнивает объекты - Сохранить консистентность — равные объекты должны иметь одинаковый хеш
- Использовать только неизменяемые атрибуты в hash()
- Добавить
__repr__()для удобства отладки
Эти три метода делают объект полностью хешируемым и пригодным для использования в словарях, множествах и других хеш-таблицах Python.