Menu

Исключения в Python: try, except, else, finally и raise

Как обрабатывать ошибки в Python - try/except/finally, ловля конкретных исключений, собственные raise и когда дать ошибке всплыть.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Ошибки - это просто значения с плохим настроением

Когда в Python что-то идёт не так - деление на ноль, чтение пропавшего файла, парсинг плохого числа - рантайм создаёт объект исключения и начинает раскручивать стек вызовов, пока его кто-то не поймает. Если никто не ловит - программа завершается и печатает трейсбэк.

Исключения сами по себе - не плохо. Это способ Python сказать «эту операцию я продолжать не могу, вот почему». Твоя работа - в каждом случае решить, какие из них ты умеешь обработать, а каким дать развиться дальше.

Базовая форма

  • try: открывает блок рискованного кода.
  • except ValueError: ловит это конкретное исключение, если оно вылетело внутри try.
  • Если исключения не было, except полностью пропускается.

Запусти сниппет с 42 - работает. Запусти с hello - сработает обработчик.

Ловим конкретные исключения

У Python иерархия типов исключений. Те, что встретишь часто:

  • ValueError - значение в каком-то смысле неправильное (int("abc"), выход за диапазон).
  • TypeError - не тот тип ("hi" + 3).
  • KeyError - ключ в словаре не найден.
  • IndexError - индекс вышел за диапазон последовательности.
  • FileNotFoundError - файл не существует.
  • ZeroDivisionError - попытка поделить на ноль.
  • AttributeError - у объекта нет запрошенного атрибута.

Лови конкретное, которое умеешь обработать:

Несколько исключений в одной ветке - через кортеж:

Заметь as e. Привязывает объект исключения к e, чтобы можно было посмотреть его сообщение или атрибуты.

Не лови всё подряд

Голый except: ловит буквально всё, включая KeyboardInterrupt (твой Ctrl-C) и системные выходы. Не используй.

except Exception: чуть лучше, но всё равно опасен - он глотает баги, которых ты не ждал, и прячет истинный источник проблем:

# Don't do this without a really good reason.
try:
    do_something()
except Exception:
    pass

Правильный ход - почти всегда ловить конкретное исключение, которое ты умеешь обработать. Если непредусмотренное исключение добралось до вершины программы, трейсбэк точно скажет, что пошло не так, - это фича, а не баг.

else и finally

У try есть две дополнительные необязательные ветви:

  • else выполняется, если блок try завершился без исключения.
  • finally выполняется в любом случае - было исключение или нет.

else - самое чистое место для «кода только при успехе»: не надо заставлять try делать больше, чем ту часть, которая реально может упасть. finally - для уборки, которая должна сработать, даже если try упал: закрыть ресурс, отпустить блокировку, восстановить состояние.

Поднимаем исключения сами

raise сигнализирует об ошибке в твоём собственном коде:

Выбирай тип исключения, подходящий к тому, что пошло не так. Сначала тянись к встроенным - ValueError, TypeError, FileNotFoundError, - а потом уже определяй свой класс.

Свои исключения

Когда встроенные не ухватывают смысл, определи собственное:

Наследуй от Exception (или более конкретного встроенного) и дай классу docstring. Обычно этого достаточно. Собственные исключения позволяют вызывающему ловить именно ту ошибку, которая осмысленна для его домена.

raise ... from ...: цепочки исключений

Когда одно исключение порождает другое - сохраняй цепочку:

from e прикрепляет исходную ошибку. При печати трейсбэка Python показывает обе - всплывшую ConfigError и породившую её FileNotFoundError. Такой след бесценен при отладке.

Контекстные менеджеры: чистый способ прибираться

finally - нормально, но для ресурсов вроде файлов контекстный менеджер (что и использует with) почти всегда лучше:

# finally version
f = open("data.txt")
try:
    data = f.read()
finally:
    f.close()

# with version
with open("data.txt") as f:
    data = f.read()

Оба безопасны. Форма with короче и применяется автоматически. Тянись к finally, только когда делаешь что-то, чего стандартная библиотека ещё не завернула в контекстный менеджер.

Когда не надо ловить

Поймать исключение - это решение: ты говоришь «я умею это обработать». Не умеешь - дай ему идти наверх. Такой код почти всегда ошибка:

try:
    do_work()
except Exception:
    pass  # silently ignore everything

Глушить ошибки - делать баги невидимыми. Лучше громко упасть, чем ковылять в несогласованном состоянии.

Подводим итог

  • try/except обрабатывает ошибки, от которых можно восстановиться.
  • Лови конкретные исключения, не Exception оптом.
  • raise сигнализирует об ошибках в твоём коде.
  • Блоки with заменяют большую часть уборки через finally.
  • Когда сомневаешься - пусть исключение всплывает.

Дальше - обзор конкретных ошибок, которые Python поднимает чаще всего - KeyError, ValueError, ModuleNotFoundError и пара других - плюс отладочные привычки, быстро их чинящие.

Часто задаваемые вопросы

Как обрабатывать ошибки в Python?

Оберни рискованный код в блок try и лови конкретное исключение в блоке except: try: risky() except ValueError: .... Необязательный else выполняется, если исключения не было; finally - в любом случае, для уборки.

Не лучше ли ловить Exception для надёжности?

Нет. Голый except: или except Exception: прячет непредусмотренные баги. Лови конкретное исключение, которое ты умеешь обработать, а всё остальное пусть всплывает - так ты увидишь настоящую проблему.

В чём разница между raise и raise from?

raise NewError(...) поднимает новое исключение. raise NewError(...) from original привязывает исходное исключение как причину, и Python показывает его в трейсбэке. Используй from, когда низкоуровневая ошибка породила более высокоуровневую, которую ты хочешь выставить наружу.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ