← Назад к вопросам
Как работает hash если использовать свой класс как ключ в словаре?
1.7 Middle🔥 151 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Hash и использование собственного класса как ключа словаря
Когда вы используете объект как ключ в словаре, Python использует механизм хеширования. Это сложный, но важный механизм для работы с коллекциями данных.
Как работает механизм хеширования
Основной принцип
Для каждого ключа словаря Python:
- Вычисляет хеш-функцию:
hash(key)→ целое число - Использует хеш для определения позиции в внутренней таблице
- Если хеши совпадают, сравнивает ключи через
==(проверка на равенство)
# Пример с встроенными типами
key1 = "hello"
key2 = 42
print(hash(key1)) # 1935066664397
print(hash(key2)) # 42
# Хеш одного и того же значения всегда одинаков
print(hash("hello") == hash("hello")) # True
Использование собственного класса как ключа
По умолчанию (не работает корректно)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(1, 2)
p2 = Point(1, 2)
data = {}
data[p1] = "first"
data[p2] = "second" # Создаст новый ключ!
print(len(data)) # 2, а не 1!
print(hash(p1) == hash(p2)) # False (разные объекты)
Почему это происходит? По умолчанию Python использует id() объекта (адрес в памяти) для хеширования. Два разных объекта имеют разные адреса, поэтому разные хеши.
Правильная реализация
Чтобы использовать объект как ключ, нужно реализовать методы __hash__() и __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
p1 = Point(1, 2)
p2 = Point(1, 2)
data = {}
data[p1] = "first"
data[p2] = "second" # Перезапишет значение!
print(len(data)) # 1
print(hash(p1) == hash(p2)) # True (одинаковые данные)
print(data[p1]) # "second"
print(p1 == p2) # True
Важные правила
1. Согласованность hash() и eq()
Правило: если a == b, то обязательно hash(a) == hash(b)
class BadPoint:
def __init__(self, x):
self.x = x
def __eq__(self, other):
return self.x == other.x
def __hash__(self):
return id(self) # ОШИБКА! Нарушает правило
p1 = BadPoint(5)
p2 = BadPoint(5)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # False - НАРУШЕНИЕ!
# Это создаст проблемы в словаре
data = {p1: "a"}
print(p2 in data) # False (найтись не может!)
2. Хешируемость должна быть неизменной
Этобиобъект был пригодным для использования как ключ, его хеш не должен меняться:
class GoodPoint:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p = GoodPoint(1, 2)
data = {p: "value"}
# Изменяем объект
p.x = 5
# Хеш изменился! Это проблема
print(hash(p)) # Другое значение
print(p in data) # False - потеряли доступ!
Решение: используйте @dataclass(frozen=True) или namedtuple:
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
data = {p1: "first"}
data[p2] = "second"
print(len(data)) # 1
print(hash(p1) == hash(p2)) # True
# Попытка изменить вызовет ошибку
# p1.x = 5 # FrozenInstanceError
3. Использование namedtuple
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p1 = Point(1, 2)
p2 = Point(1, 2)
data = {p1: "first", p2: "second"}
print(len(data)) # 1 - автоматически работает!
print(hash(p1) == hash(p2)) # True
Практический пример
from dataclasses import dataclass
@dataclass(frozen=True)
class User:
id: int
username: str
def __hash__(self):
return hash(self.id) # Хешируем только по ID
def __eq__(self, other):
if not isinstance(other, User):
return False
return self.id == other.id
# Использование в словаре
users_cache = {}
user1 = User(1, "alice")
user2 = User(1, "alice_new") # Тот же ID
users_cache[user1] = "cached_value"
users_cache[user2] = "new_value"
print(len(users_cache)) # 1 - перезапис
print(users_cache[user1]) # "new_value"
Резюме
- Реализуйте
__hash__()и__eq__()для использования класса как ключа - Хеш должен быть основан на неизменяемых данных объекта
- Правило:
a == b⟹hash(a) == hash(b) - Предпочитайте
@dataclass(frozen=True)илиnamedtupleдля иммутабельности - Никогда не меняйте объект после использования его как ключа