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