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

Не ловите всё подряд
В проекте на Python ошибка может возникнуть при чтении файла, обращении к внешнему сервису или проверке пользовательских данных. Если на каждом уровне поставить try и подавить исключение, приложение иногда продолжит работу, но данные уже могут быть неполными, а причину сбоя будет трудно найти. Если не перехватывать ничего, необработанная ошибка может прервать операцию и показать пользователю технические подробности.
Практичный подход для проекта на Python 3.12: перехватывать ожидаемые ошибки рядом с операцией, которую можно безопасно повторить или заменить понятным результатом, а на границе приложения — записывать неожиданный сбой и завершать текущую операцию. Ниже речь о стандартных исключениях и модуле logging, без привязки к веб-фреймворку.
Сначала определите, кто и что может исправить
Обработка исключения полезна, когда код после неё знает, что делать дальше. Если файл не найден — возможно, можно показать сообщение «файл недоступен». Если сервер зависимостей временно не отвечает — можно ограниченно повторить запрос. Если же неизвестно, что сломалось, продолжать выполнение рискованно: лучше остановить текущую операцию и сохранить контекст для диагностики.
Разделяйте три вещи: исключение как технический сигнал, сообщение для пользователя и запись для журнала. Пользователю не нужны путь к файлу, стек вызовов или текст ответа внутреннего сервиса. Разработчику, наоборот, может понадобиться тип ошибки и контекст операции. Не записывайте в журнал пароли, токены и другие секреты.
Перехватывайте ошибку не потому, что она неудобна, а потому, что именно этот уровень умеет безопасно решить, что делать дальше.
Граница обработки1 — место, где приложение переводит внутреннюю ошибку в понятный внешний результат: например, в сообщение интерфейсу или ответ API.
Четыре подхода и их цена
| Подход | Когда подходит | Чем платите |
|---|---|---|
| Перехватить конкретное исключение | Вы знаете, какая ошибка ожидаема и как на неё реагировать | Нужно понимать ошибки используемой операции |
| Проверить входные данные до операции | Значение можно проверить заранее, например тип или формат | Проверка не отменяет обработку ошибок при работе с файлом, сетью или диском |
| Поднять своё исключение с контекстом | Нижний уровень знает техническую причину, верхний — смысл операции | Важно не терять исходное исключение и не создавать лишние типы |
| Перехватить неожиданный сбой на границе | Нужно записать ошибку и не показать внутренности пользователю | Сбой останется сбоем: такой перехват не должен маскировать его как успех |
Ловите ошибку рядом с причиной
Указывайте конкретный тип исключения. Этот пример для Python 3.12 читает текстовый файл в UTF-8 и превращает отсутствие файла в понятный результат. Ошибка кодировки и другие сбои не скрываются.
from pathlib import Path
def read_text_file(path: Path) -> str | None:
try:
return path.read_text(encoding="utf-8")
except FileNotFoundError:
return None
text = read_text_file(Path("notes.txt"))
if text is None:
print("Файл не найден")
else:
print(text)
Здесь отсутствие файла — ожидаемая ситуация, а не повод обрывать весь процесс. Возвращаемое значение None нужно обработать: если проигнорировать его и дальше обращаться к результату как к строке, ошибка просто переместится на другое место.
Не оборачивайте каждый вызов функции в общий except Exception. Такой блок ловит слишком много разных ошибок: от ожидаемой проблемы ввода до дефекта в коде. Если после перехвата вы не можете выбрать безопасное действие, чаще всего исключению следует подняться выше.
Добавляйте смысл, но не теряйте причину
Нижний уровень может знать, что файл не открылся, а вызывающий код — что из-за этого не удалось загрузить настройки приложения. Собственное исключение позволяет передать этот смысл. Оператор raise ... from err сохраняет связь с исходной ошибкой.
from pathlib import Path
class ConfigLoadError(Exception):
"""Настройки приложения не удалось загрузить."""
def load_config(path: Path) -> str:
try:
return path.read_text(encoding="utf-8")
except (OSError, UnicodeError) as err:
raise ConfigLoadError("Не удалось прочитать файл настроек") from err
try:
config_text = load_config(Path("settings.toml"))
except ConfigLoadError as err:
print(err)
Пример намеренно не разбирает TOML и не подменяет ошибку значениями по умолчанию. Если конфигурация обязательна, запускать приложение с пустыми настройками опаснее, чем завершить загрузку с ясной причиной. В реальном приложении текст для пользователя и подробности для журнала обычно формируют раздельно.
Записывайте неожиданные ошибки один раз
На внешней границе операции можно записать стек вызовов, а затем передать ошибку выше или корректно завершить текущий запрос средствами конкретного приложения. Не логируйте одно и то же исключение в каждом слое: в журналах появятся повторные записи, но полезной информации не прибавится.
import logging
logger = logging.getLogger(__name__)
def run_operation() -> None:
try:
perform_operation()
except Exception:
logger.exception("Операция завершилась неожиданной ошибкой")
raise
def perform_operation() -> None:
raise RuntimeError("Пример сбоя")
if __name__ == "__main__":
logging.basicConfig(level=logging.ERROR)
run_operation()
logger.exception() записывает сообщение с информацией об активном исключении, поэтому его вызывают внутри блока except. Повторный raise сохраняет исключение и не превращает сбой в успешный результат. В веб-приложении вместо повторного поднятия ошибки может быть обработчик на уровне фреймворка, который формирует безопасный ответ. Его настройка зависит от фреймворка и в эту статью не входит.
Когда что выбирать
- Пользователь ввёл некорректное значение. Проверьте его на входе и верните конкретную ошибку валидации. Не ждите, пока она проявится в неожиданном месте.
- Операция иногда закономерно не удаётся. Перехватите известное исключение рядом с операцией и выберите безопасный запасной сценарий.
- Нужно добавить контекст. Поднимите исключение предметного уровня через
raise ... from err, чтобы сохранить техническую первопричину. - Сбой непредвиденный. На границе приложения запишите его один раз, не раскрывайте внутренние детали пользователю и не продолжайте операцию с сомнительными данными.
Что ломается чаще всего
- Пустой
except. Ошибка исчезает, а вызывающий код может принять незавершённую работу за успешную. - Один общий перехват на весь проект. Разные проблемы получают одинаковую реакцию, и исправить их сложнее.
- Только текст ошибки без стека. Сообщение может описать симптом, но не показать путь к месту сбоя. Для неожиданного исключения используйте логирование с информацией об исключении.
- Повторные попытки без ограничений. Временный сбой превращается в долгую задержку или серию запросов. Повторы требуют лимита, паузы и понимания, безопасно ли выполнять операцию повторно.
- Пользователь видит внутренние детали. Текст исключения может раскрыть пути, внутреннее устройство или данные запроса. Показывайте человеку нейтральное сообщение, а подробности оставляйте в защищённом журнале.
Что делать дальше
Возьмите одну критичную операцию и проследите путь ошибки: где она возникает, кто может принять решение, что увидит пользователь и попадёт ли причина в журнал. Затем проверьте три случая отдельно: ожидаемый отказ, неожиданное исключение и повторный запуск после сбоя. В проде следите не только за количеством ошибок, но и за тем, какие операции не завершились, можно ли их безопасно повторить и не дублируется ли запись в журнале.
1 Граница обработки не обязательно совпадает с границей HTTP-запроса. В консольной программе это может быть один запуск команды, а в фоновой обработке — одна задача.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: Python 3.12: встроенные исключения, Python 3.12: logging — система логирования
Частые вопросы
Нужно ли всегда перехватывать исключения?
Почему не стоит использовать except Exception везде?
Можно ли показывать пользователю текст исключения?
Как избежать повторов в логах?
Нужно ли повторять операцию после ошибки?
Подходят ли примеры для других версий Python?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.