Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как сделать класс хешируемым в Python
Хешируемые объекты могут использоваться как ключи словарей и элементы set'ов. Для этого нужно правильно реализовать методы __hash__() и __eq__().
1. Основное правило хешируемости
# Для хешируемого объекта верно: если a == b, то hash(a) == hash(b)
# Обратное неверно: разные объекты могут иметь одинаковый hash
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and 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
print(p1 is p2) # False (разные объекты)
# Можем использовать как ключи
point_dict = {p1: 'origin'}
print(point_dict[p2]) # 'origin' (p1 == p2)
# Можем добавлять в set
point_set = {p1, p2, p3}
print(len(point_set)) # 2 (p1 и p2 считаются одним)
print(point_set) # {Point(1, 2), Point(3, 4)}
2. Требования к hash()
# Обязательные правила:
# 1. Если a == b, то hash(a) == hash(b)
# 2. Хеш должен быть неизменяемым (пока объект не меняется)
# 3. Используй только неизменяемые поля для хеша
class User:
def __init__(self, id, name, email):
self.id = id # неизменяемо
self.name = name # изменяемо
self.email = email # изменяемо
def __eq__(self, other):
return isinstance(other, User) and self.id == other.id
def __hash__(self):
# Используем ТОЛЬКО неизменяемые поля (id)
return hash(self.id)
def __repr__(self):
return f'User({self.id}, {self.name})'
user1 = User(1, 'Alice', 'alice@example.com')
user2 = User(1, 'Alice Updated', 'newemail@example.com')
print(user1 == user2) # True (сравниваем по id)
print(hash(user1) == hash(user2)) # True
# Даже если меняем поля, хеш остаётся тем же
user1.name = 'Alice Changed'
print(hash(user1)) # Тот же хеш
3. Типичные ошибки
Ошибка 1: Использование изменяемых полей
class BadPoint:
def __init__(self, coords):
self.coords = coords # list - изменяемо!
def __eq__(self, other):
return isinstance(other, BadPoint) and self.coords == other.coords
def __hash__(self):
return hash(self.coords) # ОШИБКА: list not hashable!
# Это не сработает
p = BadPoint([1, 2])
print(hash(p)) # TypeError: unhashable type: 'list'
Решение:
class GoodPoint:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y)) # tuple - неизменяемо
Ошибка 2: Несогласованность eq и hash
class BadUser:
def __init__(self, id, name):
self.id = id
self.name = name
def __eq__(self, other):
# Сравниваем по обоим полям
return isinstance(other, BadUser) and self.id == other.id and self.name == other.name
def __hash__(self):
# А хешируем только по id - ОШИБКА!
return hash(self.id)
u1 = BadUser(1, 'Alice')
u2 = BadUser(1, 'Alice')
u3 = BadUser(1, 'Bob')
print(u1 == u2) # True
print(hash(u1) == hash(u2)) # True ✓
print(u1 == u3) # False
print(hash(u1) == hash(u3)) # True ✗ ОШИБКА!
# В dict/set это создаёт проблемы
my_set = {u1, u3}
print(len(my_set)) # 1 (вместо 2!) - неопределённое поведение
4. Правильная реализация для разных типов данных
Простой класс с одним идентификатором
class Product:
def __init__(self, product_id, name, price):
self.product_id = product_id # уникальный
self.name = name
self.price = price
def __eq__(self, other):
return isinstance(other, Product) and self.product_id == other.product_id
def __hash__(self):
return hash(self.product_id)
def __repr__(self):
return f'Product({self.product_id})'
p1 = Product(1, 'Laptop', 1000)
p2 = Product(1, 'Laptop Pro', 1500)
p3 = Product(2, 'Phone', 500)
products_set = {p1, p2, p3}
print(len(products_set)) # 2 (p1 и p2 одинаковые)
products_dict = {p1: 'in-stock', p3: 'out-of-stock'}
print(products_dict[p2]) # 'in-stock' (p2 == p1)
Класс с несколькими ключевыми полями
class OrderItem:
def __init__(self, order_id, item_id, quantity):
self.order_id = order_id
self.item_id = item_id
self.quantity = quantity
def __eq__(self, other):
return (isinstance(other, OrderItem) and
self.order_id == other.order_id and
self.item_id == other.item_id)
def __hash__(self):
return hash((self.order_id, self.item_id))
def __repr__(self):
return f'OrderItem({self.order_id}, {self.item_id})'
item1 = OrderItem('ORD-001', 'ITEM-A', 5)
item2 = OrderItem('ORD-001', 'ITEM-A', 10) # Другое количество, но он же
item3 = OrderItem('ORD-002', 'ITEM-A', 5)
print(item1 == item2) # True (игнорируем quantity)
print(hash(item1) == hash(item2)) # True
items_set = {item1, item2, item3}
print(len(items_set)) # 2
5. Использование @dataclass
Для dataclass хеширование настраивается автоматически:
from dataclasses import dataclass
@dataclass(frozen=True) # frozen=True делает объект неизменяемым и хешируемым
class Location:
latitude: float
longitude: float
loc1 = Location(55.7558, 37.6173) # Москва
loc2 = Location(55.7558, 37.6173) # Москва
loc3 = Location(59.9311, 30.3609) # СПб
locations = {loc1, loc2, loc3}
print(len(locations)) # 2
print(hash(loc1) == hash(loc2)) # True
Без frozen=True:
@dataclass
class MutablePoint:
x: float
y: float
p = MutablePoint(1.0, 2.0)
print(hash(p)) # TypeError: unhashable type 'MutablePoint'
6. Пользовательский хеш с колизиями
Иногда хешам позволяют иметь коллизии (разные объекты с одинаковым хешем):
class CaseInsensitiveStr:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return isinstance(other, CaseInsensitiveStr) and \
self.value.lower() == other.value.lower()
def __hash__(self):
return hash(self.value.lower())
def __repr__(self):
return f'CaseInsensitiveStr("{self.value}")'
str_set = {CaseInsensitiveStr('Hello'), CaseInsensitiveStr('HELLO'), CaseInsensitiveStr('hello')}
print(len(str_set)) # 1 (все три - одни и те же)
print(str_set)
7. Невозможные хеши
Некоторые объекты не могут быть хешируемы:
# Нельзя хешировать классы с изменяемыми полями
class MutableUser:
def __init__(self, id, permissions):
self.id = id
self.permissions = permissions # list - изменяемо
user = MutableUser(1, ['read', 'write'])
print(hash(user)) # TypeError: unhashable type
# Решение 1: использовать tuple вместо list
class ImmutableUser:
def __init__(self, id, permissions):
self.id = id
self.permissions = tuple(permissions) # неизменяемо
def __hash__(self):
return hash((self.id, self.permissions))
# Решение 2: использовать __slots__ и frozen dataclass
from dataclasses import dataclass
@dataclass(frozen=True)
class FrozenUser:
id: int
permissions: tuple
8. Пример с собственным контейнером
class UniqueUserCollection:
"""Коллекция уникальных пользователей"""
def __init__(self):
self._users = set() # используем set для уникальности
def add(self, user):
if not isinstance(user, User):
raise TypeError('Expected User instance')
self._users.add(user) # требует, чтобы User был хешируемым
def contains(self, user):
return user in self._users # O(1) благодаря hash
def __len__(self):
return len(self._users)
def __iter__(self):
return iter(self._users)
class User:
def __init__(self, id, name):
self.id = id
self.name = name
def __eq__(self, other):
return isinstance(other, User) and self.id == other.id
def __hash__(self):
return hash(self.id)
collection = UniqueUserCollection()
collection.add(User(1, 'Alice'))
collection.add(User(1, 'Alice Changed')) # Не добавится (дубликат по id)
print(len(collection)) # 1
Резюме
Для хешируемого класса нужно:
- Реализовать
__hash__()— вернуть целое число - Реализовать
__eq__()— сравнение объектов - Согласованность — если
a == bтоhash(a) == hash(b) - Использовать неизменяемые поля — для вычисления хеша
- Использовать tuple — если нужно хешировать несколько полей
- Рассмотреть frozen dataclass — для простых случаев
Основная формула:
class MyClass:
def __init__(self, id, name):
self.id = id # неизменяемо
self.name = name
def __eq__(self, other):
return isinstance(other, MyClass) and self.id == other.id
def __hash__(self):
return hash(self.id)