Какой тип возвращает метод __eq__ в Python
Что проверяют
Знаете ли вы протокол сравнения Python глубже, чем «вернёт True или False». Хороший ответ содержит NotImplemented, а следом обычно спрашивают, что станет с __hash__, когда вы переопределили __eq__.
Как отвечать
Формально __eq__ может вернуть любой объект, по соглашению возвращает bool. Важны три случая:
bool: обычный результат, когда сравнение для этой пары типов определено;NotImplemented: специальный синглтон, который означает «не умею сравнивать себя с этим типом». Python тогда пробует отражённый вызовother.__eq__(self), а если и он вернулNotImplemented, откатывается к сравнению по идентичности, то естьa is b;- произвольный объект: так делают библиотеки. У NumPy
a == bвозвращает массив булевых значений поэлементно, у SQLAlchemyUser.id == 5возвращает выражение, из которого собираетсяWHERE.
Типовая реализация начинается с if not isinstance(other, Point): return NotImplemented, дальше сравнение полей. Именно NotImplemented, а не False: второй операнд получает шанс сравнить себя сам, и сравнение с подклассом работает симметрично.
Ещё два факта, которые стоит назвать сразу:
__ne__: в Python 3 по умолчанию инвертирует результат__eq__, если это неNotImplemented, отдельно его определять не нужно;__hash__: если в классе определён__eq__, а__hash__нет, Python выставляет__hash__ = None. Объекты перестают быть хэшируемыми, их нельзя положить вsetили сделать ключомdict.@dataclassгенерирует__eq__и ведёт себя так же, пока не указанfrozen=Trueилиunsafe_hash=True.
Где спотыкаются
Говорят «всегда bool». Для большинства своих классов это так, но пример с NumPy, где if a == b: падает с ValueError про неоднозначную истинность массива, показывает, что гарантии нет.
Возвращают False для чужих типов. Сравнение перестаёт быть симметричным, а отражённый метод второго операнда вообще не вызывается.
Путают NotImplemented с NotImplementedError. Первое возвращают из методов сравнения, второе бросают из абстрактных методов, и raise NotImplementedError в __eq__ просто уронит сравнение.