Какие методы нужно реализовать для поддержки протокола хэширования в Python
Что проверяют
Понимаете ли вы, как объект становится ключом словаря или элементом множества и какой контракт связывает __hash__ и __eq__. Слово «протокол» здесь про специальные методы, а не про hashlib. Следом спрашивают, что будет, если переопределить только __eq__.
Как отвечать
Методов два:
__hash__(self): возвращает целое число, его вызываетhash(obj), а значит, иdict, иsetпри вставке и поиске;__eq__(self, other): нужен, потому что у разных объектов хэши могут совпасть, и словарь при совпадении сравнивает ключи на равенство. Для чужих типов из него возвращаютNotImplemented.
Главный инвариант: если a == b, то hash(a) == hash(b). Обратное не требуется, коллизии допустимы. Второе условие: хэш не должен меняться, пока объект лежит в коллекции, поэтому его считают только по неизменяемым полям. Типичная реализация сравнивает кортежи полей и возвращает hash((self.x, self.y)).
Поведение по умолчанию: у object хэш построен на идентичности, а равенство означает is. Если в классе определить __eq__ без __hash__, Python сам выставит __hash__ = None, и экземпляры станут нехэшируемыми: TypeError: unhashable type. Явное __hash__ = None делает то же осознанно, так устроены list и dict.
С dataclasses это стоит знать наизусть: @dataclass(frozen=True) генерирует __hash__ по полям, обычный @dataclass с eq=True ставит __hash__ в None, а unsafe_hash=True генерирует хэш и для изменяемого класса под вашу ответственность. Проверить хэшируемость можно через isinstance(obj, collections.abc.Hashable).
Где спотыкаются
Рассказывают про hashlib, SHA-256 и MD5. Это криптографические хэши байтов, к протоколу __hash__ они отношения не имеют.
Включают в хэш изменяемое поле. Объект положили в set, поменяли поле, и obj in s возвращает False, хотя объект там лежит.
Ждут, что hash("строка") одинаков между запусками. Для str и bytes хэш рандомизирован и зависит от PYTHONHASHSEED, поэтому сохранять его в базу или шардировать по нему нельзя.