Отказ специального метода сравнения поддержать пару операндов: чем отличается возврат NotImplemented от выбрасывания NotImplementedError?
NotImplemented — это сигнал интерпретатору: данный метод не поддерживает такую пару операндов, поэтому Python может попробовать другой метод сравнения или применить предусмотренное правилами сравнения поведение. NotImplementedError — исключение, которое немедленно прерывает операцию и не запускает обычный механизм подбора альтернативного метода.
В объектной модели Python специальные методы образуют протоколы взаимодействия объектов. Для бинарных операций и сравнений нужен способ отличить ситуацию «операция для этих типов не определена этим методом» от настоящей ошибки реализации.
Значение NotImplemented решает эту задачу на уровне протокола данных: метод передаёт управление механизму Python. Исключение NotImplementedError, напротив, сообщает о невозможности выполнить операцию и предназначено для обработки как ошибки.
Класс может уметь сравниваться только с объектами определённого типа. Если другой операнд имеет собственную логику сравнения, без корректного сигнала Python не сможет её использовать.
Возврат False в такой ситуации тоже может быть ошибочным: он утверждает, что объекты заведомо не равны, хотя на самом деле текущий класс лишь не знает, как их сравнивать. Выбрасывание NotImplementedError ещё жёстче: оно прерывает сравнение до проверки возможной реализации второго операнда.
При возврате NotImplemented Python рассматривает операцию как неподдержанную текущим методом. Для сравнения он может вызвать соответствующий метод второго операнда. Если и он возвращает NotImplemented, Python применяет итоговое правило конкретного оператора: например, для равенства возможна проверка идентичности, а для упорядочивания обычно возникает TypeError.
NotImplemented — специальное значение, а не исключение. Его нужно именно вернуть из метода, когда тип или комбинация операндов не поддерживаются.
Если метод выбрасывает NotImplementedError, возникает обычное исключение. Python не воспринимает его как просьбу попробовать другой метод, поэтому потенциально совместимая реализация второго операнда не используется.
Минимальный пример:
Оба класса поддерживают сравнение друг с другом, поэтому результатом будет True. Если заменить возврат NotImplemented в одном из методов на выбрасывание NotImplementedError, сравнение завершится исключением вместо передачи управления другой реализации.
Практическое правило: возвращайте NotImplemented, когда операция семантически не поддерживается для данного типа; выбрасывайте исключение, когда поддерживаемая операция столкнулась с ошибкой или нарушением контракта.
Библиотека обрабатывает собственные объекты денежных сумм и должна корректно сравнивать их с адаптером, предоставленным внешней системой. Внутренний класс знает только о совместимом интерфейсе внешнего объекта, а внешний класс умеет преобразовать внутреннюю сумму.
Вариант с возвратом False прост, но скрывает возможность второго класса выполнить сравнение. Вариант с NotImplementedError явно показывает неподдерживаемый случай, однако ломает двусторонний протокол и делает интеграцию зависимой от порядка операндов.
Выбран возврат NotImplemented для неизвестных типов. В результате Python получает возможность вызвать логику второго операнда, а действительно некорректные значения обрабатываются отдельной проверкой и явным исключением. Это сохраняет расширяемость протокола и не маскирует ошибки.
Вопрос: Можно ли возвращать NotImplemented из обычной пользовательской функции и ожидать автоматического подбора альтернативы?
Ответ: Вернуть это значение можно, поскольку оно является объектом Python. Но специальная логика подбора применяется только там, где её определяет конкретный протокол интерпретатора, например при вызове методов сравнения или бинарных операций. В обычной функции NotImplemented будет просто возвращённому вызывающему коду значением и само по себе не запустит никаких альтернативных вызовов.
Вопрос: Что произойдёт, если оба метода сравнения вернут NotImplemented?
Ответ: Результат зависит от оператора. Для == и != Python использует предусмотренное резервное поведение, связанное с идентичностью объектов. Для операторов упорядочивания вроде < или >= отсутствие поддерживаемой реализации обычно приводит к TypeError.
Вопрос: Когда уместно выбрасывать NotImplementedError в классе?
Ответ: Это уместно, когда метод объявлен как часть интерфейса, но конкретный класс не предоставляет его реализацию, либо когда вызов означает ошибку проектирования. Для специального метода сравнения такой подход не заменяет возврат NotImplemented: он сообщает об ошибке выполнения, а не об отказе участвовать в протоколе выбора реализации.