hash и == для экземпляров класса в Python

hash и == для экземпляров класса в Python

@python_quiz

Разберем этот квиз

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

Условие (исходный код)

python

Этот код печатает два булевых значения: сравнение хешей и сравнение самих объектов через ==.

Поведение по умолчанию: identity-based сравнение и хеш

По умолчанию класс наследует реализации object.__eq__ и object.__hash__. Они работают так:

  • object.__eq__ сравнивает идентичность объектов (эквивалентно is).
  • object.__hash__ возвращает хеш, основанный на id объекта (в CPython — связанный с адресом/идентификатором).

Поскольку u1 и u2 — разные экземпляры, у них разные id, значит обычно и hash(u1) != hash(u2) и u1 != u2. То есть ожидаемый вывод:

text

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

python

Обычно вы увидите разные id и разные хеши, и, следовательно, два значения False.

Анализ вариантов: почему возможны другие комбинации

Варианты вроде "True True", "True False" или "False True" тоже возможны, но зависят от того, как вы реализуете __eq__ и __hash__.

  1. 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
`

  1. 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
`

Замечание: специально делать одинаковый хеш — плохая идея (низкая производительность хеш-структур).

  1. False True

- Сценарий: объекты считаются равными по __eq__, но хеши разные. Это технически возможно, если вы переопределите __eq__ и __hash__ неконсистентно (или вернёте разные хеши). Однако в Python общий контракт для хешируемых объектов требует, чтобы равные объекты имели одинаковый хеш. Нарушение этого приведёт к некорректной работе словарей и множеств.
- Важный нюанс: если вы определите __eq__ и не определите __hash__, то в Python 3 __hash__ станет None и экземпляры станут непригодными для хеширования (hash(u1) вызовет TypeError). Поэтому комбинация с вызовом hash без реализации __hash__ невозможна — вы получите исключение.

  1. False False

- Сценарий: исходный случай (по умолчанию) — разные объекты, разные id, разные хеши, не равны. Это то, с чем вы столкнётесь в приведённом в квизе коде.

Правила и рекомендации

  • По умолчанию объекты сравниваются по идентичности. Чтобы сравнивать по содержимому — реализуйте __eq__.
  • Если вы реализовали __eq__, подумайте о __hash__:

- Если объекты должны быть хешируемыми и сравниваться по значениям, реализуйте __hash__ согласованно с __eq__ (например, комбинируйте хеши полей).
- Если объекты изменяемы, лучше не делать их хешируемыми (или делать их __hash__ = None), чтобы избежать проблем в словарях/множествах.

  • Для удобства используйте dataclasses:
python

frozen=True делает экземпляры неизменяемыми, dataclass автоматически реализует __eq__ и __hash__ корректно.

Выводы

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

Report Page