← Назад к вопросам
Что нужно сделать, чтобы использовать экземпляры собственного класса в качестве ключей в словаре?
1.8 Middle🔥 131 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование экземпляров класса в качестве ключей словаря
По умолчанию объекты Python используют идентификатор (id) как ключ, что не всегда удобно. Для правильной работы нужно реализовать два метода.
1. Что нужно реализовать
Требование: объект можно использовать как ключ, только если он hashable и immutable.
Для этого нужно определить два метода:
class MyClass:
def __hash__(self):
"""Возвращает целое число (хеш объекта)"""
return hash((self.attr1, self.attr2))
def __eq__(self, other):
"""Проверяет равенство двух объектов"""
if not isinstance(other, MyClass):
return False
return self.attr1 == other.attr1 and self.attr2 == other.attr2
2. Практический пример
class User:
def __init__(self, user_id: int, name: str):
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(id={self.user_id}, name={self.name})"
# Теперь можем использовать объект как ключ
user1 = User(1, "Alice")
user2 = User(1, "Alice") # Такой же ID
user3 = User(2, "Bob")
user_data = {}
user_data[user1] = "admin"
user_data[user2] = "moderator" # Перезапишет user1 (т.к. __eq__ совпадает)
user_data[user3] = "user"
print(user_data) # {User(id=1, name=Alice): 'moderator', User(id=2, name=Bob): 'user'}
print(user_data[user1]) # 'moderator' (т.к. user1 == user2)
3. Важные правила
Правило 1: если a == b, то hash(a) == hash(b)
# Неправильно:
class BadUser:
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
# Проверяем всё, но хеш только по name
return (self.name == other.name and
self.age == other.age) # BAD!
# Если два пользователя с одинаковыми name, но разными age:
# hash(user1) == hash(user2), но user1 != user2
# Словарь сломается!
Правило 2: значения для хеша должны быть immutable
# Неправильно:
class BadPoint:
def __init__(self, x, y):
self.coords = [x, y] # Mutable list!
def __hash__(self):
return hash(tuple(self.coords))
def __eq__(self, other):
return self.coords == other.coords
p1 = BadPoint(1, 2)
points = {p1: "first"}
p1.coords[0] = 5 # Изменили объект
# Теперь hash изменился, но словарь не знает про это
print(p1 in points) # False! (но объект находится в словаре)
Решение: используй immutable объекты
class GoodPoint:
def __init__(self, x: int, y: int):
self._x = x
self._y = y
def __hash__(self):
return hash((self._x, self._y))
def __eq__(self, other):
if not isinstance(other, GoodPoint):
return False
return self._x == other._x and self._y == other._y
# Опционально: make truly immutable
def __setattr__(self, name, value):
if hasattr(self, '_x'):
raise AttributeError("GoodPoint is immutable")
super().__setattr__(name, value)
4. Использование @dataclass
Самый удобный способ с Python 3.7+:
from dataclasses import dataclass
from typing import NamedTuple
# Способ 1: dataclass с frozen=True
@dataclass(frozen=True) # Immutable и автоматический __hash__
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True
points = {p1: "first", p2: "second"} # p2 перезапишет p1
print(points) # {Point(x=1, y=2): 'second'}
# Способ 2: NamedTuple (уже hashable)
from typing import NamedTuple
class PointNT(NamedTuple):
x: int
y: int
p1 = PointNT(1, 2)
p2 = PointNT(1, 2)
points = {p1: "first", p2: "second"} # Работает как dataclass
5. Обычный dataclass без frozen (осторожно!)
from dataclasses import dataclass
@dataclass
class User:
user_id: int
name: str
# По умолчанию НЕ hashable!
user = User(1, "Alice")
# users = {user: "admin"} # TypeError: unhashable type: 'User'
# Нужно явно сделать hashable:
@dataclass(frozen=True)
class User:
user_id: int
name: str
# Теперь hashable и immutable
6. Пример с более сложным классом
from dataclasses import dataclass
from typing import Tuple
@dataclass(frozen=True)
class Movie:
title: str
year: int
director: str
def __hash__(self):
# Можно переопределить для контроля
# Например, хешировать только title и year
return hash((self.title, self.year))
def __eq__(self, other):
if not isinstance(other, Movie):
return False
# Два фильма одинаковые, если title и year совпадают
# (режиссер может быть разным в разных изданиях)
return self.title == other.title and self.year == other.year
# Использование
movies_ratings = {}
movie1 = Movie("Inception", 2010, "Nolan")
movie2 = Movie("Inception", 2010, "Someone Else") # Разный режиссер
movie3 = Movie("Interstellar", 2014, "Nolan")
movies_ratings[movie1] = 8.8
movies_ratings[movie2] = 8.9 # Перезапишет movie1 (т.к. __eq__)
movies_ratings[movie3] = 8.7
print(movies_ratings)
print(movie1 in movies_ratings) # True (т.к. movie1 == movie2)
7. Когда НЕ использовать объекты как ключи
# Проблема: объекты с many-to-one связями
class Author:
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
return self.name == other.name
author1 = Author("Tolkien")
author2 = Author("Tolkien") # Разные объекты, но == True
# Если нужен уникальный идентификатор, используй ID, не name
class GoodAuthor:
def __init__(self, author_id, name):
self.author_id = author_id
self.name = name
def __hash__(self):
return hash(self.author_id) # Уникальный ID
def __eq__(self, other):
return self.author_id == other.author_id
8. Best practices
# ✓ Хорошо: используй frozen dataclass
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
host: str
port: int
config_cache = {}
config = Config("localhost", 8000)
config_cache[config] = {"settings": "..."}
# ✓ Хорошо: используй только уникальные поля
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):
return isinstance(other, User) and self.user_id == other.user_id
# ✗ Плохо: изменяемые объекты
class MutableUser:
def __init__(self, user_id):
self.user_id = user_id
self.roles = [] # Можно изменять!
# Не делай hashable изменяемые классы
Итог
Для использования объектов как ключей словаря:
- Реализуй
__hash__()— возвращающий целое число - Реализуй
__eq__()— сравнение объектов - Убедись, что
hash(a) == hash(b)еслиa == b - Используй только immutable поля для хеша
- Лучше всего —
@dataclass(frozen=True)из коробки