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

Как работает hash если использовать свой класс как ключ в словаре?

1.7 Middle🔥 151 комментариев
#Python Core

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

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

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

Hash и использование собственного класса как ключа словаря

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

Как работает механизм хеширования

Основной принцип

Для каждого ключа словаря Python:

  1. Вычисляет хеш-функцию: hash(key) → целое число
  2. Использует хеш для определения позиции в внутренней таблице
  3. Если хеши совпадают, сравнивает ключи через == (проверка на равенство)
# Пример с встроенными типами
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 == bhash(a) == hash(b)
  • Предпочитайте @dataclass(frozen=True) или namedtuple для иммутабельности
  • Никогда не меняйте объект после использования его как ключа
Как работает hash если использовать свой класс как ключ в словаре? | PrepBro