← Назад к вопросам
Какие знаешь магические (dunder) методы для работы с хэшируемыми объектами в Python?
1.7 Middle🔥 81 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь магические (dunder) методы для работы с хэшируемыми объектами в Python
Магические методы (dunder methods, double underscore) — это специальные методы Python, которые начинаются и заканчиваются двойным подчеркиванием. Для хешируемых объектов наиболее важны методы __hash__() и __eq__().
Основные методы для хешируемости
1. __hash__(self) — вычисление хеша объекта
Возвращает целое число, используется для размещения объекта в хеш-таблице.
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def __hash__(self):
"""Вычислить хеш на основе email"""
return hash(self.email)
def __eq__(self, other):
"""Два пользователя равны, если у них одинаковый email"""
if not isinstance(other, User):
return False
return self.email == other.email
# Использование
user1 = User("Alice", "alice@example.com")
user2 = User("Alice", "alice@example.com")
user3 = User("Bob", "bob@example.com")
print(hash(user1)) # Одинаковое значение
print(hash(user2)) # для user1 и user2
print(user1 == user2) # True
print(user1 == user3) # False
# Можно использовать в множеством
users = {user1, user2, user3} # user1 и user2 - это один элемент
print(len(users)) # 2
2. __eq__(self, other) — проверка равенства
Определяет, когда два объекта считаются равными.
class Product:
def __init__(self, id, name, price):
self.id = id
self.name = name
self.price = price
def __hash__(self):
"""Хеш по ID"""
return hash(self.id)
def __eq__(self, other):
"""Равны по ID"""
if not isinstance(other, Product):
return NotImplemented
return self.id == other.id
# Правило: если a == b, то hash(a) == hash(b)
p1 = Product(1, "Laptop", 1000)
p2 = Product(1, "Laptop", 1000) # Тот же ID
p3 = Product(2, "Phone", 500) # Другой ID
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True - это ВАЖНО!
# В словаре и множестве
products = {p1, p2, p3}
print(len(products)) # 2, так как p1 == p2
product_dict = {p1: "In stock"}
product_dict[p2] = "Updated" # Обновляет существующий ключ
print(len(product_dict)) # 1
Дополнительные dunder методы
3. __repr__(self) — строковое представление
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 NotImplemented
return self.x == other.x and self.y == other.y
def __repr__(self):
"""Красивый вывод в интерпретаторе"""
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(repr(p)) # Point(3, 4)
print(str(p)) # Point(3, 4)
4. __lt__, __le__, __gt__, __ge__ — сравнения
class Version:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __hash__(self):
return hash((self.major, self.minor, self.patch))
def __eq__(self, other):
if not isinstance(other, Version):
return NotImplemented
return (self.major, self.minor, self.patch) == \
(other.major, other.minor, other.patch)
def __lt__(self, other):
"""Меньше"""
return (self.major, self.minor, self.patch) < \
(other.major, other.minor, other.patch)
def __repr__(self):
return f"{self.major}.{self.minor}.{self.patch}"
v1 = Version(1, 2, 3)
v2 = Version(1, 2, 4)
v3 = Version(1, 2, 3)
print(v1 == v3) # True
print(v1 < v2) # True
print(hash(v1) == hash(v3)) # True
Правила для хешируемости
# ✅ ПРАВИЛО 1: Если a == b, то hash(a) == hash(b)
class Good:
def __init__(self, value):
self.value = value
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
if not isinstance(other, Good):
return NotImplemented
return self.value == other.value
# ❌ ПРАВИЛО 2: Нарушение (плохо!)
class Bad:
def __init__(self, value, mutable_list):
self.value = value
self.mutable_list = mutable_list
def __hash__(self):
# ПРОБЛЕМА: хеш зависит от неизменяемого значения
return hash(self.value)
def __eq__(self, other):
# А равенство проверяет изменяемый список
if not isinstance(other, Bad):
return NotImplemented
return self.value == other.value and \
self.mutable_list == other.mutable_list
b1 = Bad(1, [1, 2, 3])
b2 = Bad(1, [1, 2, 3])
print(b1 == b2) # True
print(hash(b1) == hash(b2)) # True (совпадает)
b2.mutable_list.append(4)
print(b1 == b2) # False! А хеш остался прежним - ОШИБКА!
Практические примеры
Использование в кэшировании (LRU Cache)
from functools import lru_cache
class CacheableObject:
def __init__(self, data):
self.data = data
def __hash__(self):
return hash(self.data)
def __eq__(self, other):
if not isinstance(other, CacheableObject):
return NotImplemented
return self.data == other.data
def __repr__(self):
return f"CacheableObject({self.data})"
@lru_cache(maxsize=128)
def expensive_operation(obj):
"""Результаты кэшируются на основе хеша объекта"""
return obj.data ** 2
obj1 = CacheableObject(5)
obj2 = CacheableObject(5)
result1 = expensive_operation(obj1) # Вычисляется
result2 = expensive_operation(obj2) # Берется из кэша
Использование в словарях
class Employee:
def __init__(self, employee_id, name):
self.employee_id = employee_id
self.name = name
def __hash__(self):
return hash(self.employee_id)
def __eq__(self, other):
if not isinstance(other, Employee):
return NotImplemented
return self.employee_id == other.employee_id
def __repr__(self):
return f"Employee({self.employee_id}, {self.name})"
emp1 = Employee(1, "Alice")
emp2 = Employee(1, "Alice Updated")
emp3 = Employee(2, "Bob")
employee_data = {emp1: "Senior Developer"}
employee_data[emp2] = "Lead Developer" # Обновляет emp1
employee_data[emp3] = "Manager"
print(len(employee_data)) # 2
print(employee_data)
Важные моменты
- Только неизменяемые объекты должны быть хешируемы — избегай зависимостей от изменяемых атрибутов
- hash() должен быть идентичен для равных объектов — это критично
- Используй tuple для композитных хешей —
hash((a, b, c)) - Вернуть NotImplemented в eq — если тип не совпадает
- Документируй поведение — объясни, на основе каких полей вычисляется хеш