Как Python определяет уникальность объекта для ключей словаря
Что проверяют
Понимаете ли вы, что уникальность определяется парой методов, а не одним хешем.
Как отвечать
Словарь работает в два шага. Сначала считается hash(key), и по нему выбирается ячейка. Затем среди кандидатов в этой ячейке ключи сравниваются через __eq__. Уникальность определяется именно результатом сравнения, а хеш нужен, чтобы быстро сузить круг кандидатов.
Отсюда контракт: равные объекты обязаны иметь равный хеш, а хеш не должен меняться, пока объект используется как ключ. Обратное не требуется: разные объекты могут иметь одинаковый хеш, это обычная коллизия, и она разрешается сравнением.
Практические следствия:
- по умолчанию у пользовательского класса хеш считается по идентичности объекта, а равенство сравнивает ссылки. Поэтому два экземпляра с одинаковыми полями будут разными ключами;
- определив только
__eq__, вы получите объект, который вообще нельзя положить в словарь: Python выставляет__hash__вNone; - определив только
__hash__, получите объект-ключ, который не находится по логически равному значению; - изменение поля, участвующего в хеше, «теряет» запись в словаре: она остаётся в старой ячейке;
- встроенные неизменяемые типы (числа, строки, кортежи,
frozenset) хешируемы; списки, словари и множества нет; - у чисел разных типов с одним значением хеш совпадает, поэтому
d[1]иd[1.0]это один и тот же ключ.
Самый простой способ получить корректную пару методов это @dataclass(frozen=True).
Где спотыкаются
Считают, что достаточно совпадения хешей.
Кладут в словарь изменяемые объекты и меняют их поля.
Не знают, что True и 1 это один ключ, и удивляются потерянному значению.