Достаточно ли определить только метод __hash__, чтобы объект класса корректно работал как ключ в словаре Python
Что проверяют
Знаете ли вы контракт между __hash__ и __eq__ и понимаете ли, что произойдёт, если определить только один из них. Ответ «нужны оба» верен, но интереснее объяснить, почему.
Как отвечать
Формально объект станет ключом и с одним __hash__: словарь примет его, потому что хешируемость есть. Но вести себя он будет не так, как ожидается.
Причина в том, что при совпадении хеша словарь сравнивает ключи через __eq__. Если равенство не определено, наследуется реализация из object, то есть сравнение по идентичности. Значит два разных экземпляра с одинаковыми полями и одинаковым хешем окажутся разными ключами, и поиск по логически равному объекту вернёт KeyError.
class Point:
def __init__(self, x, y): self.x, self.y = x, y
def __hash__(self): return hash((self.x, self.y))
d = {Point(1, 2): "a"}
d[Point(1, 2)] # KeyError: равенство по идентичностиОбратная ситуация тоже стоит упоминания: если определить только __eq__, Python выставляет __hash__ = None, и объект перестаёт быть хешируемым вовсе. Это сделано намеренно, чтобы не нарушался контракт.
Сам контракт формулируется так: равные объекты обязаны иметь равные хеши, а хеш не должен меняться, пока объект используется как ключ. Обратное неверно, совпадение хешей у неравных объектов допустимо, это обычная коллизия.
Практически оба метода почти всегда считают по одному и тому же набору полей, и проще всего это получить через @dataclass(frozen=True), который генерирует и то, и другое.
Где спотыкаются
Определяют __eq__ для удобства сравнения и внезапно теряют возможность класть объект в множество или словарь.
Строят хеш по изменяемому полю, и объект теряется в словаре после изменения.
Считают, что совпадение хешей означает равенство объектов.