Ответ
Да, для этого необходимо корректно реализовать два специальных метода: __hash__ и __eq__. Это позволит использовать экземпляры класса как ключи в словарях или элементы множеств.
Ключевые правила:
- Метод
__eq__определяет логику сравнения объектов на равенство. - Метод
__hash__должен возвращать целочисленное значение, которое неизменно на протяжении всего времени жизни объекта. - Если
a == b(т.е.__eq__возвращаетTrue), то обязательно должно выполнятьсяhash(a) == hash(b).
Пример реализации неизменяемого (immutable) класса:
class ImmutablePoint:
"""Пример хэшируемого класса, представляющего точку."""
__slots__ = ('_x', '_y') # Оптимизация памяти и запрет на добавление атрибутов
def __init__(self, x, y):
# Атрибуты делаем приватными и не изменяемыми после создания
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __hash__(self):
# Хэш вычисляется на основе кортежа из неизменяемых полей
return hash((self._x, self._y))
def __eq__(self, other):
if not isinstance(other, ImmutablePoint):
return NotImplemented
return (self._x, self._y) == (other._x, other._y)
# Использование
p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(1, 2)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True
points_set = {p1, p2}
print(points_set) # {<__main__.ImmutablePoint object at ...>} - только один элемент
print(p1 in points_set) # True
Важное предупреждение: Если класс является изменяемым (mutable), и его состояние влияет на __hash__, то изменение объекта после помещения в словарь или множество приведет к его "потере" в этой коллекции, так как хэш изменится. Поэтому хэшируемые классы лучше делать неизменяемыми.
Ответ 18+ 🔞
А, ну это же классика, ёпта! Смотри, если ты хочешь, чтобы твои объекты вели себя как нормальные граждане в словарях и множествах, им нужно выучить два магических заклинания: __hash__ и __eq__. Без этого они — просто манда с ушами, которую никуда не воткнешь.
Главное, что надо запомнить, чтобы не накрыться медным тазом:
__eq__— это когда ты сравниваешь, один и тот же это объект или нет. Типа, «ты мне ровня или хуй с горы?».__hash__— должен выдать одно и то же число на протяжении всей жизни объекта. Как отпечаток пальца, только для питона.- САМОЕ ВАЖНОЕ ПРАВИЛО, ЕБААААТЬ: Если
a == b(то есть__eq__сказал «да, это одно и то же»), то иhash(a) == hash(b)ОБЯЗАТЕЛЬНО должен быть правдой. Иначе всё полетит в пиздопроебибну, и доверия к твоему коду будет ебать ноль.
Вот смотри, как это делается на примере точки, которую нельзя сломать:
class ImmutablePoint:
"""Точка, которую не переделаешь под себя — как характер у тёщи."""
__slots__ = ('_x', '_y') # Чтоб лишних атрибутов не налепили, хитрая жопа
def __init__(self, x, y):
# Делаем атрибуты приватными и замораживаем нахуй
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __hash__(self):
# Хэш делаем из кортежа этих самых полей. Просто и гениально.
return hash((self._x, self._y))
def __eq__(self, other):
# Если нам подсунули хуй в пальто, а не точку — говорим «я не могу это сравнить»
if not isinstance(other, ImmutablePoint):
return NotImplemented
# Сравниваем как кортежи — и волнение ебать куда-то делось
return (self._x, self._y) == (other._x, other._y)
# Проверяем, работает ли эта магия
p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(1, 2)
print(p1 == p2) # True — они одинаковые, ёпта!
print(hash(p1) == hash(p2)) # True — и хэши сошлись, красота!
points_set = {p1, p2} # Пытаемся засунуть в множество
print(points_set) # {<__main__.ImmutablePoint object at ...>} — ага, дубля сожрало!
print(p1 in points_set) # True — и он там есть, всё пучком
И последнее, но овердохуища важное: Если твой класс можно менять после создания (мутабельный), и от этого меняется его хэш — ты играешь с огнём. Представь: положил объект в словарь, потом его изменил, хэш поменялся, и всё — объект как в черную дыру провалился, найти его уже не получится. Поэтому либо делай класс неизменяемым, как в примере, либо будь готов к неожиданностям. Сам от себя охуеешь, когда это всплывёт.