Исключения
Исключение в Python — это не код возврата и не особый режим интерпретатора, а обычный объект, наследник BaseException. Оператор raise помечает такой объект как текущее исключение и запускает размотку стека — кадр за кадром, пока не найдётся кадр, у которого есть try с веткой, подходящей по isinstance. Если до самого верха ничего не подошло, управление уходит в sys.excepthook, тот печатает traceback в sys.stderr, и процесс завершается с кодом 1. С версии 3.11 механизм «нулевой стоимости» — таблица обработчиков лежит рядом с байт-кодом, и try, в котором ничего не бросили, не стоит на исполнении вообще ничего. Отсюда и питоновский стиль EAFP — сначала пробуем, ошибку ловим, а не проверяем условия заранее.
Питон-специфика начинается сразу же, и её удобно назвать заранее. Корень иерархии — BaseException, а не Exception, и SystemExit, KeyboardInterrupt, GeneratorExit намеренно вынесены за пределы Exception, поэтому except Exception не съедает Ctrl+C, а голый except: съедает. Ветки проверяются сверху вниз, и срабатывает только первая подошедшая, так что общий базовый класс над конкретным превращает конкретный в мёртвый код — и интерпретатор об этом промолчит. Блок else выполняется исключительно при чистом проходе, finally — всегда, включая тот случай, когда return внутри finally молча выбрасывает летящее исключение. А голый raise — единственная форма повторного подъёма, которая не дописывает в traceback кадры, не участвовавшие в аварии. Разберите каждый механизм в слоях ниже.
Карта темы
- Обработка и raise — как
raiseразматывает стек, что вообще можно возбуждать и куда девается непойманное исключение. - Иерархия BaseException — почему
SystemExit,KeyboardInterruptиGeneratorExitживут внеExceptionи когдаSyntaxErrorвсё-таки ловится. - Порядок веток except — правило первого совпадения, подклассы раньше базовых и как
except*его переворачивает. - Блок else — код, который выполняется только при отсутствии исключения, и почему это не то же самое, что положить его в
try. - Блок finally — гарантированная уборка и ловушка с
returnвнутриfinally, стирающим исключение. - Повторный подъём и сцепление — голый
raiseпротивraise e, неявный__context__и явный__cause__черезraise ... from. - Пользовательские исключения — собственная иерархия ошибок приложения, полезная нагрузка в атрибутах и
args. - Предупреждения — модуль
warningsкак нефатальный канал и его фильтр «один раз на место вызова».
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Писать голый except: вместо except Exception | Ветка ловит BaseException целиком — Ctrl+C и sys.exit() перестают работать, процесс становится неубиваемым штатным способом |
Ставить except Exception выше except ValueError | Конкретная ветка недостижима, но CPython не выдаёт ни ошибки, ни предупреждения — мёртвый код видит только линтер |
| Ждать, что сработают все подходящие ветки | Срабатывает ровно одна — первая совпавшая сверху; остальные пропускаются, даже если они точнее |
Считать, что else выполняется при исключении | Наоборот — else выполняется только при чистом проходе try, и всегда до finally |
Ставить return, break или continue внутри finally | Летящее исключение бесследно исчезает, а значение из try подменяется — Python 3.14 предупреждает об этом SyntaxWarning |
Писать raise e вместо голого raise | В traceback дописывается кадр обработчика, и цепочка перестаёт указывать на настоящее место аварии |
Наследовать пользовательское исключение от BaseException | Все существующие except Exception перестают его ловить, и ошибка приложения роняет процесс наравне с Ctrl+C |
Считать, что warnings.warn останавливает выполнение | Предупреждение уходит в фильтр и печатается в stderr, код идёт дальше; исключением оно становится только при фильтре error |
Значение для собеседований
Тема входит в обязательный минимум и проверяется двумя заходами. Первый — на знание устройства: просят нарисовать верх иерархии и объяснить, почему except Exception не ловит KeyboardInterrupt. Правильный ответ отделяет ошибки от сигналов управления — SystemExit, KeyboardInterrupt и GeneratorExit держат вне Exception именно для того, чтобы «поймать все ошибки» не означало «поймать команду завершиться». Дальше почти всегда идёт вопрос про голый except: — его надо назвать эквивалентом except BaseException и объяснить, чем это опасно в долгоживущем сервисе.
Второй заход — код на экране. Классика жанра — сниппет с except Exception выше except ValueError и просьба объяснить, почему вторая ветка недостижима; здесь важно назвать правило первого совпадения, а не «Python выбирает самый точный обработчик». Следом дают try/finally с return в finally и спрашивают, что вернёт функция и куда делось исключение. Затем — цепочку из except с raise e и вопрос про traceback, где ждут голый raise и разницу между __context__ и __cause__. Типичная ошибка на всём этом наборе одна и та же — кандидат помнит синтаксис, но не помнит семантику порядка и времени выполнения, поэтому проговаривайте вслух: ветки проверяются сверху вниз, else только при чистом проходе, finally всегда, raise без аргумента ничего не портит.