Обработка исключений в Python
Обработка исключений — это способность перехватывать и управлять ошибочными ситуациями в программе, показывать понятные сообщения и гарантировать, что ресурсы корректно освобождаются. Независимо от того, создаёте ли вы веб-сайт, API или модуль, ясная обработка ошибок улучшает опыт пользователя и облегчает отладку.
Как работают исключения в Python
Когда вы «поднимаете» (raise) исключение, вы говорите интерпретатору прервать нормальный поток выполнения и перейти к ближайшему обработчику исключений. Метафорически это похоже на попытку поднять тяжесть: если не получается, нужно сообщить об этом — и обработчик выполняет эту роль.
Типичная конструкция для обработки выглядит так:
try:
"code to be executed"
except:
"error message"
Вы также можете добавлять блок finally — код в нём выполняется всегда, независимо от того, было исключение или нет. Это удобно для освобождения ресурсов (закрыть файл, закрыть соединение и т. п.).
Пример с finally:
try:
print(9+6)
except:
print("error message")
finally:
print("please restart")
Вывод:
15
please restart
Блок else выполняется только если внутри try исключений не произошло — удобно для кода, который должен выполняться только при успехе основной операции.
try:
C = 2 + B
except:
print("B needs to be defined")
else:
print(u"Added successfully! The result is %s"%(C))
Вывод: B needs to be defined
Если определить B и повторить:
try:
B = 5
C = 2 + B
except:
print("B needs to be defined")
else:
print(u"Added successfully! The result is %s"%(C))
Вывод: Added successfully! The result is 7
Когда использовать исключения
Исключения — мощный инструмент, но их нельзя использовать как замену контролю корректности данных во всём коде. Рекомендации:
- Используйте исключения для обработки непредвиденных ситуаций или ошибок ввода от пользователя и внешних систем.
- Не подавляйте ошибки полностью — логируйте их или пробрасывайте дальше после частичной обработки.
- Не применяйте «голый» except: он скрывает тип ошибки и затрудняет отладку.
В общем, исключения удобны для обработки ошибок внешнего происхождения (ввод, сеть, файл), а для локальной валидации полезнее явные проверки.
Приёмы обработки исключений — практические примеры
Ниже — перевод и сохранение исходных примеров с пояснениями.
try:
C = 2 + B
except NameError as err:
print(err, ":", "B needs to be defined, please")
else:
print(u"Added successfully! The result is %s"%(C))
Вывод: name 'B' is not defined : B needs to be defined, please
В этом примере используется конкретный тип исключения NameError — это хорошая практика: сначала проверяем стандартное исключение, затем дополняем сообщение своим текстом.
Можно перехватывать несколько исключений отдельно:
try:
B = 5
C = 2 + B
D = float(6)
F = 7/0
except NameError as err:
print(err,":", "B needs to be defined, please")
except ValueError as val:
print(val,":", "You can't convert that data")
except ZeroDivisionError as zeroerr:
print(zeroerr,":", "You can't divide a number by zero")
else:
print(u"Operation successfull! The results are: %s, %s, and %s"%(C, D, F))
Вывод: division by zero : You can't divide a number by zero
Если операция деления допустима (например, 7/5), то выполнится блок else и покажет результаты.
Пользовательские исключения
Иногда нужна собственная семантика ошибки — для этого определяют классы исключений, унаследованные от Exception.
class connectionError(RuntimeError):
def __init__(self, value):
self.value = value
try:
raise connectionError("Bad hostname")
except connectionError as err:
print(err.value)
Вывод: Bad hostname
Или более гибкий пример с передачей сообщения:
class errors(Exception):
pass
class sixFiveError(errors):
def __init__(self, value, message):
self.value = value
self.message = message
try:
raise sixFiveError(6-5,"This substraction is not allowed")
except sixFiveError as e:
print("There was an error:", e.message)
Вывод: There was an error: This substraction is not allowed
Пример функции, которая поднимает пользовательское исключение при попытке сложить не float-значения:
# First call the base exception classes:
class errors(Exception):
pass
# Next, derive your own exception from the base class:
class FloatError(errors):
def __init__(self, value, message):
self.value = value
self.message = message
# Create a function to add two floats:
def addTwoFloat(a, b):
if (type(a) and type(b)) != float:
raise FloatError(a+b,"Numbers must be float to add")
else:
print(a + b)
addTwoFloat(4, 7)
Вывод: __main__.FloatError: (11, 'Numbers must be float to add')
Важно: пользовательские исключения видны только в том модуле, где они определены, если вы явно не импортируете их.
Лучшие практики и шаблоны
- Перехватывайте самые конкретные исключения, а не все подряд.
- Избегайте пустого except: используйте except Exception as e, а ещё лучше — конкретный тип.
- Логируйте трассировки ошибок (traceback) — это поможет при диагностике.
- Не используйте исключения для управления обычным потоком программы (используйте проверки).
- При создании пользовательских исключений наследуйте от Exception или от подходящего подкласса.
- Добавляйте понятные сообщения ошибок, ориентированные на пользователя или на лог для разработчика.
Пример рекомендованного шаблона:
import logging
logger = logging.getLogger(__name__)
try:
result = do_io_operation()
except (IOError, OSError) as e:
logger.exception("Ошибка ввода-вывода при do_io_operation: %s", e)
raise
else:
return result
finally:
cleanup()Когда исключения не подходят
- Для внутренней валидации данных лучше явные проверки (if/else), если это ожидаемая ситуация.
- Для высокопроизводительных циклов частые поднятия исключений могут быть дороже, чем проверка условий заранее.
- Если исключение локализуется и не несёт полезной информации — лучше вернуть специальный код ошибки или объект результата с полем status.
Альтернативные подходы
- Возврат объектов результата (Result/Either pattern) вместо исключений — чаще используется в функциональном стиле.
- Контроль потоков с помощью валидации и предварительных проверок.
- Использование типов и проверок (typing, pydantic) для ловли ошибок на этапе разработки.
Ментальные модели и эвристики
- «Проверяй заранее или обрабатывай исключение» — choose: EAFP (Easier to Ask Forgiveness than Permission) vs LBYL (Look Before You Leap). Python склоняется к EAFP: пробуй и обрабатывай исключения, если операция обычно успешна.
- Делайте ошибки явными: пользователь должен понять причину и, если возможно, как её исправить.
Чеклист по ролям
Разработчик:
- Перехватить конкретные исключения.
- Логировать с трассировкой.
- Не скрывать исключения.
Тестировщик:
- Проверить сценарии ошибок: ввод, сеть, диск.
- Убедиться, что сообщения пользователю понятны.
DevOps/инфраструктура:
- Настроить сбор логов и алерты по критическим исключениям.
- Отслеживать повторяющиеся ошибки и их частоту.
Критерии приёмки
- При некорректном вводе пользователь получает понятное сообщение и инструкция по исправлению.
- Системные исключения логируются с traceback и метаданными (время, идентификатор запроса).
- Ресурсы (файлы, соединения) всегда освобождаются.
Шпаргалка по стилю кода
- Не используйте bare except.
- Используйте context managers (with) для файлов и сетевых соединений.
- Для пользовательских ошибок создавайте небольшие и семантически значимые классы исключений.
Пример context manager:
with open('data.txt') as f:
process(f)Диаграмма принятия решения
flowchart TD
A[Начать операцию] --> B{Ожидается ошибка?}
B -- Да --> C[Проверить условие заранее]
C --> D{Условие прошло?}
D -- Да --> E[Выполнить операцию]
D -- Нет --> F[Вернуть ошибку/код]
B -- Нет --> G[Попробовать выполнить]
G --> H{Исключение поднято?}
H -- Да --> I[Перехватить конкретное исключение]
I --> J[Залогировать, сообщить пользователю, при необходимости пробросить]
H -- Нет --> EТестовые сценарии и кейсы
- Попытка открыть несуществующий файл должна вызывать OSError, сообщение логируется, пользователь получает дружелюбный текст.
- Деление на ноль вызывает ZeroDivisionError; при этом сервис должен сообщить о некорректных данных, не падать.
- Неправильный тип в API-запросе должен возвращать 4xx с описанием ошибки.
Краткий глоссарий
- Исключение — сигнал о непредвиденной ситуации во время выполнения программы.
- raise — оператор для явного поднятия исключения.
- try — блок, где выполняется потенциально опасный код.
- except — блок, который перехватывает исключения.
- finally — блок, выполняемый всегда.
- пользовательское исключение — класс исключения, определённый разработчиком.
Заключение
Исключения — стандартный инструмент управления ошибками в Python. Правильное использование try/except/else/finally, создание осмысленных пользовательских исключений и хорошая практика логирования делают ваш код надёжнее и понятнее пользователю. Выберите стратегию (EAFP или LBYL) в зависимости от сценариев и нагрузки, и придерживайтесь конкретных перехватов вместо голых except.
Краткое резюме:
- Перехватывайте конкретные исключения.
- Логируйте и давайте понятные сообщения.
- Используйте пользовательские исключения для бизнес-логики.
- Тестируйте сценарии ошибок и освобождение ресурсов.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone