Что возвращает метод __eq__ в Python
Что проверяют
Знаете ли вы протокол сравнения в Python, а не только то, что == вызывает __eq__. Ответ «возвращает True или False» неполный, и интервьюер ждёт именно этой оговорки. Следом почти всегда спрашивают про __hash__ и про то, почему после переопределения __eq__ объект перестал помещаться в set.
Как отвечать
__eq__ может вернуть любой объект. Обычно это bool, но есть два важных исключения:
NotImplemented: специальная константа, которая значит «не умею сравнивать с этим типом». Тогда Python пробует отражённый вызовother.__eq__(self). Если и он вернулNotImplemented,==откатывается к проверке идентичности, то есть кa is b;- не-bool: NumPy на
a == bвозвращает массив поэлементных результатов, pandas возвращаетSeries, а в SQLAlchemyUser.name == "bob"даёт объект выражения, из которого строитсяWHERE. Поэтомуif arr == other:на массиве падает сValueErrorо неоднозначной истинности.
Дальше про связку с хэшем. Если класс определяет __eq__ и не определяет __hash__, Python ставит __hash__ = None, и экземпляры становятся нехэшируемыми: их нельзя положить в set или сделать ключом dict. Правило: равные объекты обязаны иметь равный хэш, поэтому хэш считают по тем же полям, что и равенство, и только по неизменяемым.
!= в Python 3 отдельно определять не нужно: __ne__ по умолчанию инвертирует результат __eq__, если тот не NotImplemented.
На практике руками __eq__ пишут всё реже: @dataclass генерирует его сравнением кортежей полей (только для объектов того же класса), а @dataclass(frozen=True) добавит и __hash__.
Где спотыкаются
Возвращают False для чужого типа вместо NotImplemented. Сравнение становится несимметричным: подкласс или другой тип уже не может ответить за себя.
Путают NotImplemented с NotImplementedError. Первое возвращают, второе бросают, а raise NotImplemented просто падает с TypeError.
Переопределяют __eq__ и через неделю ловят TypeError: unhashable type на ровном месте. Или, наоборот, добавляют __hash__ по изменяемым полям: объект меняется, пока лежит в set, и найти его там больше нельзя.