Ошибки — это просто значения с плохим настроением
Когда в 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, когда низкоуровневая ошибка породила более высокоуровневую, которую ты хочешь выставить наружу.