Ошибки против исключений: что выбрать в Python
Исключение удобно для сбоя, который нельзя обработать на месте, но превращать каждый ожидаемый отказ в traceback — плохая идея. Разбираем подходы на Python 3.11 и показываем, где ловить исключения, а где возвращать результат явно.

Сначала определите, кто и где обработает сбой
В Python 3.11 исключение подходит для неожиданной ошибки или отказа, который нужно передать выше по стеку. Если же отсутствие записи или неверный ввод — нормальный вариант работы программы, обработайте его рядом с источником: верните явный результат или используйте проверку. Иначе код либо засорит логи traceback-ами, либо спрячется за слишком широким except.
Ниже — практическое сравнение трёх подходов: исключений, специальных значений вроде None и явного результата с признаком успеха. Выбор влияет не только на стиль: от него зависит, заметите ли вы ошибку подключения к базе, не перепутав её с отсутствующей записью.
Три способа сообщить о проблеме
Исключение: ошибка передаётся вызывающему коду
Исключение прерывает обычный ход выполнения и поднимается вверх, пока его не поймает подходящий обработчик. Так работают, например, ошибки преобразования типов и файловых операций. Это удобно, когда вызывающий код действительно может выбрать, что делать дальше.
Не ловите всё подряд. Перехватывайте конкретный тип и только там, где есть осмысленная реакция: повторить запрос, показать сообщение пользователю или завершить обработку задачи. В Python 3.11 базовый класс Exception позволяет поймать почти все обычные исключения, поэтому широкий перехват легко скроет программную ошибку.
from pathlib import Path
def read_settings(path: Path) -> str:
try:
return path.read_text(encoding="utf-8")
except FileNotFoundError as error:
raise RuntimeError(f"Файл настроек не найден: {path}") from error
try:
settings = read_settings(Path("settings.ini"))
except RuntimeError as error:
print(f"Запуск невозможен: {error}")
Здесь функция добавляет контекст и сохраняет исходную причину через from error. В реальном приложении последнюю границу обработки стоит связать с логированием или сообщением пользователю, а не оставлять только print.
Специальное значение: ожидаемый отказ без исключения
Если функция ищет необязательную настройку, отсутствие значения может быть штатным сценарием. Тогда она может вернуть None, а вызывающий код явно проверит результат. Цена простоты — None нельзя без риска использовать для разных причин отказа. Если запись отсутствует, база недоступна и запрос некорректен, один и тот же результат стирает важные различия.
def find_label(labels: dict[str, str], key: str) -> str | None:
return labels.get(key)
label = find_label({"home": "Главная"}, "profile")
if label is None:
print("Подпись не задана")
else:
print(label)
Аннотация str | None соответствует синтаксису типов Python 3.10 и новее. В Python 3.9 и старше для такого типа нужна другая форма записи.
Явный результат: успех и отказ различаются в интерфейсе
Когда отказ ожидаем, но у него несколько значимых причин, результат можно описать отдельной структурой. Например, загрузчик может сообщить, что запись не найдена, не превращая это в исключение. Такой контракт требует больше кода, зато вызывающая сторона обязана разобрать результат.
В Python нет встроенного универсального типа Result с единым стандартным интерфейсом. Если вводите собственный, зафиксируйте его форму и типы в проекте, а не полагайтесь на неявные договорённости вроде «пустая строка означает ошибку».
Исключение отвечает на вопрос «что пошло не так и кому это передать». Результат отвечает на вопрос «какой из предусмотренных исходов произошёл». Не смешивайте эти роли без причины.
Термин: ошибка домена — ожидаемый отказ, связанный с правилами самой задачи: например, пользователь ввёл недопустимый статус заказа. Это не то же самое, что сбой инфраструктуры вроде недоступной базы данных.
Сравнение подходов
| Подход | Когда подходит | Цена и риск |
|---|---|---|
| Исключение | Сбой, который должен прервать текущую операцию и дойти до обработчика | Удобно передавать причину; широкий перехват может скрыть дефект |
None или другое специальное значение | Один понятный штатный исход, например необязательное значение не найдено | Минимум обвязки; несколько причин отказа легко спутать |
| Явный объект результата | Ожидаемых исходов несколько и вызывающему коду нужно различать их | Контракт понятнее; нужно определить и поддерживать собственный тип |
Где ловить исключения и что не делать
Ставьте обработчик на границе, где можно принять решение. Веб-обработчик может превратить известную ошибку домена в ответ клиенту; фоновый worker — записать причину и решить, повторять ли задачу. Низкоуровневая функция обычно не знает, что правильнее для конкретного приложения, поэтому ей стоит либо вернуть ожидаемый результат, либо поднять исключение дальше.
- Не используйте
except:без типа. Он ловит также исключения, которые обычно нужны для остановки выполнения, напримерKeyboardInterrupt. - Не превращайте любую ошибку в
None. Это может замаскировать отказ сети или базы под обычное отсутствие данных. - Не ловите исключение без действия. Пустой блок
exceptскрывает причину сбоя и затрудняет диагностику. - Не применяйте исключения вместо обычной проверки. Если неверный пользовательский ввод ожидаем, проверьте его рядом с получением и верните понятную ошибку валидации.
- Не логируйте один и тот же сбой на каждом уровне. Выберите границу, где ошибка становится окончательной или где принимается решение о повторе.
Обычная проверка не всегда устраняет гонку. Например, проверка «файл существует» перед чтением не гарантирует, что файл не исчезнет между двумя операциями. В таком случае обработайте конкретное исключение самой операции чтения.
Когда что выбирать
Для Python 3.11 ориентируйтесь не на правило «исключения дорогие» или «исключения выразительнее», а на смысл исхода.
- Значение может отсутствовать по правилам функции, а причины ровно одна — верните
Noneи явно проверьте его. - Есть несколько штатных исходов, которые вызывающий код обрабатывает по-разному, — задайте явный тип результата с отдельными вариантами.
- Операция не может выполниться из-за сбоя или вызывающая сторона должна решить, как продолжать, — поднимите исключение.
- Можно восстановиться на месте, например повторить чтение после временного сбоя, — ловите только конкретное исключение и ограничьте число повторов.
Отдельно решите, что попадёт в логи. Пользователю обычно не нужен traceback, а инженеру может быть нужен тип исключения и контекст операции. Не включайте в сообщения секреты, содержимое токенов и персональные данные.
Что делать дальше
Возьмите одну функцию, которая сейчас возвращает пустую строку, число или None при любой проблеме. Запишите все возможные исходы и отметьте, какие из них штатны. Для штатных исходов выберите явный результат, для сбоя — конкретное исключение; затем добавьте тесты на каждый вариант и проверьте, что на верхней границе ошибка логируется один раз. Для деталей синтаксиса и поведения исключений сверяйтесь с документацией именно той версии Python, на которой работает приложение.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: Python 3.11: Errors and Exceptions, Python 3.11: Built-in Exceptions
Частые вопросы
Исключения в Python всегда нужно считать ошибками программы?
Когда лучше вернуть None, а не поднять исключение?
Можно ли ловить Exception и продолжать работу?
Что будет, если забыть обработать исключение?
Нужно ли использовать собственные классы исключений?
Подойдут ли примеры для другой версии Python?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.