Ответ
Чтобы объект класса можно было использовать в качестве ключа словаря (dict) или элемента множества (set), он должен быть хешируемым. Это означает, что у него должен быть постоянный хеш-код на протяжении всей его жизни.
Основные требования:
- Неизменяемость (Immutability): Все атрибуты, участвующие в вычислении хеша, должны быть неизменяемыми. Если хеш изменится после добавления объекта в словарь, объект станет недоступным.
- Реализация
__hash__(self): Метод должен возвращать целочисленное значение (хеш). - Реализация
__eq__(self, other): Метод для сравнения объектов. Еслиa == b, то обязательно должно бытьhash(a) == hash(b).
Современный подход (Python 3.7+): dataclasses
Самый простой и надежный способ — использовать декоратор @dataclass(frozen=True). Он автоматически генерирует __eq__ и __hash__ на основе атрибутов класса.
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
points = {p1: "Начало"}
print(p1 == p2) # True
print(points[p2]) # Выведет: "Начало"
# p1.x = 3 # Вызовет FrozenInstanceError
Классический подход: ручная реализация
Если вы не можете использовать dataclasses, методы нужно реализовать вручную:
class PointManual:
def __init__(self, x, y):
self._x = x
self._y = y
def __eq__(self, other):
if not isinstance(other, PointManual):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self):
# Хеш вычисляется на основе кортежа, т.к. кортежи неизменяемы
return hash((self._x, self._y)) Ответ 18+ 🔞
Смотри, есть такая хуйня: если ты хочешь запихнуть свой самопальный объект в словарь как ключ или в сет, он должен быть хешируемым. А это, блядь, целая наука, на самом деле.
Короче, правила такие, ёпта:
- Незыблемость, как скала. Все поля, по которым считается хеш, должны быть неизменными. Представь: положил ты объект в словарь, а потом взял и поменял ему атрибут. Хеш изменился, и всё — объект потерялся в недрах словаря, как хуй в проруби. Достать не получится, пиздец.
- Нужен метод
__hash__(self). Он должен выдать целое число — это и есть твой хеш. - И метод
__eq__(self, other)в придачу. Чтобы сравнивать объекты. Главное правило, которое нельзя нарушать, даже если мать родная попросит: еслиa == bвозвращаетTrue, тоhash(a) == hash(b)обязательно должно бытьTrue. Иначе тебе пизда, и интерпретатор тебя сожрёт.
Современный способ, для ленивых и умных: dataclasses
Не еби себе мозг, просто используй @dataclass(frozen=True). Он сам всё сделает: и __eq__, и __hash__ нагенерит на основе твоих полей. А frozen=True — это чтобы объект стал неизменяемым, как ледышка, и никто не смог его случайно испортить.
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
points = {p1: "Начало"}
print(p1 == p2) # True
print(points[p2]) # Выведет: "Начало"
# p1.x = 3 # Вызовет FrozenInstanceError — попробуй, сука, измени!
Старый дедовский способ, если ты мазохист
Ну или если ты на древнем коде работаешь. Тогда всё руками, как в старину:
class PointManual:
def __init__(self, x, y):
self._x = x
self._y = y
def __eq__(self, other):
if not isinstance(other, PointManual):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self):
# Хеш считаем от кортежа из полей — кортежи неизменяемы, и хеш у них стабильный, красота.
return hash((self._x, self._y))
Вот и вся магия. Главное — не накосячить с неизменяемостью, а то будет тебе волнение ебать, когда ключи начнут пропадать.