Обработка исключений в Python: руководство с примерами и методиками

Зачем читать это руководство
Кто это нужно: разработчикам, техлидам и тестировщикам, работающим с Python. Цель: научить правильно обрабатывать ошибки, чтобы код был надёжным, понятным и удобным для пользователей.
Как работает обработка исключений в Python
Исключение — это сигнал о том, что какая-то часть кода не смогла выполниться. Когда возникает исключение, выполнение текущего блока прерывается и интерпретатор ищет соответствующий обработчик.
Краткое определение: исключение — объект, указывающий на ошибочную ситуацию во время выполнения программы.
Основная идея: обернуть рисковый код в блок try, а возможные ошибки перехватывать в блоке except. Блок finally выполняется всегда, а else — только если исключений не было.
Основные конструкции: try, except, else, finally
Простейшая форма:
try:
# код, который может вызвать исключение
result = 10 / x
except ZeroDivisionError:
print("Деление на ноль запрещено")Блок else выполняется, если исключение не произошло:
try:
total = a + b
except NameError:
print("Переменные не определены")
else:
print("Сумма вычислена:", total)Блок finally выполняется всегда и полезен для очистки ресурсов:
try:
f = open('data.txt')
data = f.read()
except IOError as e:
print('Ошибка ввода/вывода:', e)
finally:
if 'f' in locals():
f.close()Совет: используйте менеджеры контекста (with) вместо finally для работы с ресурсами, когда это возможно.
Ловите конкретные исключения, не используйте bare except
Нельзя оставлять голый except:
try:
do_work()
except:
pass # плохая практика: скрывает ошибкиЛучше явно указать тип исключения:
try:
do_work()
except ValueError as e:
logger.error('Неверное значение: %s', e)
raiseВажно: если вы не можете обработать исключение локально — логируйте контекст и пробрасывайте дальше (raise), чтобы не потерять причину ошибки.
Исключения с несколькими except и объединённый перехват
Можно перехватывать разные исключения отдельно или группировать их в кортеже:
try:
x = int(user_input)
y = 10 / x
except (ValueError, TypeError) as parse_err:
print('Неверный ввод:', parse_err)
except ZeroDivisionError as zde:
print('Деление на ноль')Искусство сообщения об ошибках: контекст и читаемость
Хорошее сообщение об ошибке должно:
- кратко объяснять, что пошло не так;
- указывать, где это произошло (контекст);
- предлагать действие пользователю, если возможно.
Пример:
except FileNotFoundError as e:
raise RuntimeError('Не удалось загрузить конфигурацию. Проверьте путь: {}'.format(config_path)) from eОператор “from” сохраняет цепочку исключений (exception chaining) и помогает при отладке.
Пользовательские исключения
Иногда стандартных исключений недостаточно. Тогда определяют свои классы исключений, унаследованные от Exception.
Пример простого пользовательского исключения:
class AppError(Exception):
"""Базовое исключение приложения"""
pass
class ValidationError(AppError):
def __init__(self, field, message):
self.field = field
self.message = message
def __str__(self):
return f"Ошибка в поле {self.field}: {self.message}"
# Использование
try:
raise ValidationError('email', 'некорректный формат')
except ValidationError as e:
print(e)Правило: пользовательские исключения должны нести смысловую нагрузку и быть легко идентифицируемыми.
Примеры распространённых ошибок и как их обрабатывать
- NameError: доступ к несуществующей переменной — проверяйте входные данные или объявляйте значения по умолчанию.
- ValueError/TypeError: неверный тип/значение — проверяйте предусловия функции и ввод.
- ZeroDivisionError: проверяйте делитель до операции.
- IOError/FileNotFoundError: используйте проверки наличия файла или менеджер контекста.
Практический приём: валидируйте внешние данные на входе и выбрасывайте понятные ValidationError вместо того, чтобы позволять цепочке исключений ломаться глубже по стеку.
Альтернативы обработке исключений
- Явная валидация и возврат кодов ошибок. Полезно в низкоуровневом коде или там, где исключения дорогие по производительности.
- Использование типов результата, например, возвращать Optional/Union[Result, Error] (в Python — соглашение с None или объектом-результатом).
Когда не использовать исключения:
- для обычного контроля потока (исключения должны быть для экстраординарных ситуаций);
- в горячем коде, где исключение вызывается часто и оказывает существенную нагрузку.
Мини-методология: как внедрять обработку исключений в проект
- Определите базовое приложение-исключение (AppError) и семейство дочерних исключений (ValidationError, ExternalServiceError и т.д.).
- Внешний API/слой приложения ловит AppError и преобразует их в понятные сообщения для пользователя/клиента.
- Внутри библиотек — ловите и пробрасывайте (raise from) с контекстом.
- Логируйте ошибки с достаточной информацией (адрес, входные данные, состояние), но не записывайте чувствительные данные.
- Покрывайте критические пути тестами и имитируйте ошибки внешних сервисов.
Чеклист по ролям
Разработчик:
- ловить конкретные исключения;
- не подавлять исключения без логирования;
- использовать raise from при перепаковке ошибок.
Тестировщик:
- писать тесты на ожидаемые исключения;
- имитировать недоступность внешних сервисов.
Техлид/архитектор:
- определить схему пользовательских исключений;
- стандартизировать формат сообщений и уровни логирования.
Шпаргалка: часто используемые шаблоны
- Перехват и логирование с повторным выбросом:
try:
risky()
except Exception as e:
logger.exception('Ошибка при выполнении risky')
raise- Перехват с замещением сообщения и сохранением причины:
try:
parse_config()
except (ValueError, KeyError) as e:
raise RuntimeError('Ошибка парсинга конфигурации') from e- Менеджер контекста вместо finally:
with open('file.txt') as f:
data = f.read()Модели принятия решений и эвристики
- Эвристика 1: если ошибка вызвана внешним вводом — валидируйте и выбрасывайте ValidationError.
- Эвристика 2: если ошибка может быть восстановлена локально — обрабатывайте её; если нет — пробрасывайте.
- Эвристика 3: в публичном API возвращайте контролируемые ошибки/коды, не дампьте стектрейсы пользователю.
Когда обработка исключений терпит неудачу — типичные кейсы
- Подавление исключений (pass) скрывает реальные проблемы и усложняет отладку.
- Ловля Exception без повторного выброса может привести к неконсистентному состоянию программы.
- Логирование чувствительных данных (паролей, токенов) при ошибках — риск для безопасности.
Риски и mitigations
- Риск: потеря трассировки ошибки. Митигирование: использовать raise from и логирование с exception().
- Риск: утечка ресурсов. Митигирование: менеджеры контекста и тесты.
- Риск: раскрытие внутренних деталей пользователю. Митигирование: маскировать детали и возвращать понятные сообщения.
Критерии приёмки
- Все критические пути имеют тесты, проверяющие ожидаемую обработку ошибок.
- Нельзя иметь bare except в кодовой базе без явного комментария и проверки.
- Все пользовательские исключения документированы и содержат понятные сообщения.
Примеры тест-кейсов и приёмки
- Ввод неправильного формата приводит к ValidationError и корректному сообщению пользователю.
- Отсутствие файла приводит к FileNotFoundError, который преобразуется в понятную ошибку уровня приложения.
- При недоступности внешнего сервиса генерируется ExternalServiceError и лог с таймстемпом.
Краткая галерея крайних случаев
- Исключения, которые нужно игнорировать: временные оповещения в фоне, где повторная попытка ожидаема.
- Исключения, которые нужно немедленно фиксировать: нарушения целостности данных, потеря связи с базой.
1‑строчный глоссарий
- Исключение: объект об ошибке во время выполнения.
- raise: оператор для возбуждения исключения.
- try/except: блок для обработки исключений.
- raise from: связывает новое исключение с исходным.
Резюме
Обработка исключений — не просто способ скрыть ошибки. Это инструмент коммуникации с пользователем и способом поддержания надёжности системы. Ловите конкретные исключения, логируйте контекст, используйте пользовательские классы для доменной семантики и избегайте подавления ошибок без следа.
Важно: протестируйте сценарии ошибок, документируйте формат исключений и всегда думайте о безопасности при логировании.
Похожие материалы
Переход с Microsoft Office на WPS Office
Как изменить цвет текста с помощью CSS
CSS тени: box-shadow и text-shadow
Как встроить MP3 на сайт — HTML5, Google Drive, CMS
Начать сайт с HTML5 Boilerplate — быстрое руководство