Разберите последствие: что нарушит декоратор, если он перехватывает BaseException вместо Exception и возвращает запасное значение?
Такой декоратор может подавить не только обычные ошибки приложения, но и сигналы управления выполнением: KeyboardInterrupt, SystemExit, а в современных версиях Python также asyncio.CancelledError. В результате программа может не завершиться по запросу пользователя, задача — не отмениться, а вызывающий код получит правдоподобный, но некорректный результат.
Обычно декоратор должен перехватывать Exception, а не BaseException. Исключения уровня BaseException следует перехватывать только в узком инфраструктурном коде, если для каждого из них явно определено корректное поведение.
Иерархия исключений Python разделяет обычные ошибки выполнения и сигналы, управляющие жизненным циклом программы. Exception предназначен для большинства ошибок приложения, тогда как BaseException является более широким основанием и включает исключения, которые обычно не следует скрывать.
Такое разделение позволяет написать общий обработчик ошибок, не мешая остановке программы, прерыванию пользователем или отмене асинхронной задачи. Декораторы повторных попыток, кэширования и подстановки результата особенно чувствительны к этой границе.
Представим декоратор, который при любой перехваченной ошибке возвращает запасное значение. Для обычного ValueError это может быть полезно: например, сервис вернёт значение по умолчанию. Но если тот же обработчик перехватит KeyboardInterrupt, оператор не сможет штатно остановить процесс сочетанием клавиш.
При перехвате asyncio.CancelledError задача может продолжить работу вместо освобождения ресурсов и завершения отменённой операции. Ещё опаснее ситуация, когда вызывающий код считает запасное значение успешным результатом и продолжает работу с частично выполненной операцией.
except Exception перехватывает экземпляры Exception и её подклассов, но не KeyboardInterrupt, SystemExit и BaseException напрямую. except BaseException охватывает всю иерархию, поэтому его использование в универсальном декораторе фактически превращает сигналы остановки и отмены в обычные прикладные ошибки.
Минимальная безопасная схема для обычного обработчика ошибок выглядит так:
Здесь KeyboardInterrupt и SystemExit проходят наружу. В асинхронном коде обработчик также не должен без специальной причины поглощать asyncio.CancelledError; обычно его либо не перехватывают, либо после очистки ресурсов повторно возбуждают.
Иногда инфраструктурному уровню требуется перехватить BaseException, например для журналирования любой причины завершения. Но журналирование не должно автоматически означать подавление: после записи события исключение обычно нужно повторно возбудить. Особенно важно не возвращать запасное значение после частичного выполнения операции, если оно маскирует её незавершённость.
В HTTP-клиенте применяют декоратор повторных попыток. Первый вариант перехватывает BaseException и повторяет запрос после любой причины. Его преимущество — единая логика, но недостатки критичны: отменённый запрос может продолжить выполняться, а остановка процесса будет задержана.
Второй вариант перехватывает только Exception. Он не вмешивается в сигналы отмены и завершения, но по-прежнему обрабатывает сетевые ошибки и ошибки декодирования, если они представлены подклассами Exception. Выбран второй вариант; для отдельных исключений дополнительно задают белый список повторяемых ошибок, чтобы не повторять необратимые операции.
Результат — отмена запроса действительно прекращает работу, ресурсы освобождаются в finally, а повторные попытки применяются только к ожидаемым временным сбоям. Это немного увеличивает настройку декоратора, зато исключает скрытые изменения жизненного цикла задачи.
except Exception безопасен в асинхронной функции?Нет. В актуальных версиях Python asyncio.CancelledError относится к BaseException, поэтому обычный except Exception его не поглощает. Однако обработчик может отдельно перехватить CancelledError или использовать слишком широкий except BaseException, и тогда отмена будет нарушена.
Даже при перехвате только Exception нужно учитывать побочные эффекты: если декоратор возвращает запасное значение после частичного изменения состояния, ошибка не исчезает, а лишь маскируется. Поэтому обработчик должен быть согласован с атомарностью операции и гарантией очистки ресурсов.
BaseException?Повторное возбуждение сохраняет исходный сигнал для вызывающего кода: тот сможет завершить процесс, отменить задачу или выполнить собственную политику обработки. Возврат специального значения стирает различие между успешным результатом и аварийным завершением.
Если исключение нужно журналировать, его можно записать и затем передать дальше. Возвращать значение допустимо только для явно выбранных прикладных исключений, когда контракт функции прямо описывает такое поведение.
BaseException в декораторе для гарантированного журналирования?Да, но такой декоратор должен быть наблюдателем, а не обработчиком ошибки: после журналирования он должен сохранить исходную семантику завершения. Практически это означает повторное возбуждение исключения, а очистку ресурсов следует выполнять в finally или средствами контекстного менеджера.
Нужно также учитывать ошибки самого журналирования. Если запись в журнал может выбросить новое исключение и заменить исходное, диагностика станет менее точной; поэтому надёжный инфраструктурный код отдельно защищает операцию логирования и не подменяет первоначальную причину без необходимости.