hash и == для экземпляров класса в Python
@python_quizРазберем этот квиз
Кратко: если вы не определяете __eq__ и __hash__ в своём классе, сравнение и хеширование объектов по умолчанию опираются на идентичность объектов (id). Рассмотрим подробнее на примерах и разберём возможные варианты результатов.
Условие (исходный код)

Этот код печатает два булевых значения: сравнение хешей и сравнение самих объектов через ==.
Поведение по умолчанию: identity-based сравнение и хеш
По умолчанию класс наследует реализации object.__eq__ и object.__hash__. Они работают так:
- object.
__eq__сравнивает идентичность объектов (эквивалентно is). - object.
__hash__возвращает хеш, основанный на id объекта (в CPython — связанный с адресом/идентификатором).
Поскольку u1 и u2 — разные экземпляры, у них разные id, значит обычно и hash(u1) != hash(u2) и u1 != u2. То есть ожидаемый вывод:

Дополнительная проверка:

Обычно вы увидите разные id и разные хеши, и, следовательно, два значения False.
Анализ вариантов: почему возможны другие комбинации
Варианты вроде "True True", "True False" или "False True" тоже возможны, но зависят от того, как вы реализуете __eq__ и __hash__.
- True True
- Сценарий: вы реализуете __eq__ (сравнение по значимым полям, например name) и согласованно реализуете __hash__ (например hash(self.name)). Тогда два объекта с одинаковым содержимым будут равны и иметь одинаковый хеш.
- Пример:
`python
class User:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return isinstance(other, User) and self.name == other.name
def __hash__(self):
return hash(self.name)
u1 = User('ann'); u2 = User('ann')
print(hash(u1) == hash(u2), u1 == u2) # True True
`
- True False
- Сценарий: хеши совпадают (намеренно или по совпадению), но объекты не равны. Это можно получить, например, если вы явно переопределите __hash__ так, что он возвращает одно и то же значение для разных объектов, но __eq__ оставите по умолчанию (identity).
- Пример:
`python
class User:
def __init__(self, name):
self.name = name
def __hash__(self):
return 42 # искусственная коллизия
u1 = User('ann'); u2 = User('ann')
print(hash(u1) == hash(u2), u1 == u2) # True False
`
Замечание: специально делать одинаковый хеш — плохая идея (низкая производительность хеш-структур).
- False True
- Сценарий: объекты считаются равными по __eq__, но хеши разные. Это технически возможно, если вы переопределите __eq__ и __hash__ неконсистентно (или вернёте разные хеши). Однако в Python общий контракт для хешируемых объектов требует, чтобы равные объекты имели одинаковый хеш. Нарушение этого приведёт к некорректной работе словарей и множеств.
- Важный нюанс: если вы определите __eq__ и не определите __hash__, то в Python 3 __hash__ станет None и экземпляры станут непригодными для хеширования (hash(u1) вызовет TypeError). Поэтому комбинация с вызовом hash без реализации __hash__ невозможна — вы получите исключение.
- False False
- Сценарий: исходный случай (по умолчанию) — разные объекты, разные id, разные хеши, не равны. Это то, с чем вы столкнётесь в приведённом в квизе коде.
Правила и рекомендации
- По умолчанию объекты сравниваются по идентичности. Чтобы сравнивать по содержимому — реализуйте
__eq__. - Если вы реализовали
__eq__, подумайте о__hash__:
- Если объекты должны быть хешируемыми и сравниваться по значениям, реализуйте __hash__ согласованно с __eq__ (например, комбинируйте хеши полей).
- Если объекты изменяемы, лучше не делать их хешируемыми (или делать их __hash__ = None), чтобы избежать проблем в словарях/множествах.
- Для удобства используйте dataclasses:

frozen=True делает экземпляры неизменяемыми, dataclass автоматически реализует __eq__ и __hash__ корректно.
Выводы
- В исходном коде без
__eq__и__hash__вы получите сравнение по идентичности: обычно два разных экземпляра дадут False False. - Другие комбинации возможны при явном переопределении
__eq__и/or__hash__, но важно соблюдать контракт: равным объектам — одинаковый хеш. - Для правильной семантики и удобства используйте dataclasses или аккуратно реализуйте оба метода одновременно.