Что случится без обработки ошибок
Функция input() всегда возвращает строку, даже если пользователь напечатал одни цифры. Чтобы получить из неё число, строку передают в int() или float(). Если человек по ошибке наберёт слово вместо цифры, например «двадцать» вместо 20, функция int() не сможет разобрать такую строку и поднимет исключение ValueError. Без обработки это исключение долетает до самого верха программы, интерпретатор печатает трассировку вызовов и завершает процесс целиком. Пользователь в этот момент видит текст ошибки вместо результата работы программы, а весь код, написанный после неудачной строки, просто не выполняется.
То же самое происходит при делении на ноль. Выражение 10 / 0 поднимает исключение ZeroDivisionError ровно в момент вычисления, и если рядом нет обработчика, программа останавливается точно так же, как при неверном вводе. Учебные программы часто просят пользователя ввести делитель самостоятельно, и здесь риск получить ноль почти неизбежен — кто-то обязательно наберёт его по невнимательности или ради проверки, что будет дальше.
Как try-except перехватывает ошибку
Конструкция try-except делит код на две части. Код внутри блока try Python выполняет как обычно, строка за строкой. Если во время выполнения возникает исключение, интерпретатор сразу прерывает блок try и передаёт управление в блок except, минуя все оставшиеся строки try, даже те, что шли сразу за проблемной. Программа при этом продолжает работу дальше вместо аварийного завершения. Если исключения не возникло вовсе, блок except не выполняется, и программа переходит к коду, который стоит после всей конструкции.
Перехват конкретных типов ошибок
После слова except можно указать конкретный тип исключения, и тогда блок сработает только на ошибку этого типа, а любая другая ошибка пролетит мимо и всё равно завершит программу. Такое поведение полезнее, чем кажется на первый взгляд, потому что разные ошибки требуют разной реакции. Ошибка ValueError означает, что строка не превращается в число — стоит попросить пользователя ввести данные заново. Ошибка ZeroDivisionError означает, что делитель оказался нулём — здесь уместнее сообщить именно про деление и предложить ввести любое число, кроме нуля.
- except ValueError перехватывает неудачное преобразование строки в число через int() или float()
- except ZeroDivisionError перехватывает деление на ноль в любом арифметическом выражении
- except (ValueError, ZeroDivisionError) в одной строке перехватывает сразу оба типа и запускает общий блок обработки
- несколько блоков except подряд позволяют завести для каждой ошибки собственное сообщение и собственные действия
Почему голый except опасен
Запись except без указания типа перехватывает вообще любое исключение, какое только способен породить код внутри try. На первый взгляд это удобно — не нужно перечислять типы по одному. На практике приём прячет настоящие причины сбоев. Опечатка в имени переменной, обращение к несуществующему методу, ошибка в логике самого кода — всё это тоже исключения, и голый except проглотит их вместе с ожидаемой ошибкой ввода. Программа продолжит работу так, будто ничего не произошло, а баг останется незамеченным и всплывёт значительно позже, когда найти его причину станет заметно труднее.
Более безопасный вариант — перечислить именно те типы ошибок, которые действительно ожидаются в этом месте кода: ValueError, ZeroDivisionError, KeyError и подобные им. Если нужен запасной вариант на случай непредвиденной ошибки, лучше указать except Exception — этот класс охватывает почти все обычные ошибки программы, но оставляет в стороне системные сигналы вроде принудительной остановки программы комбинацией клавиш, которые голый except перехватил бы вместе со всем остальным.
Дополнительные блоки else и finally
Блок else выполняется только тогда, когда код внутри try прошёл целиком без единого исключения. Его удобно использовать для действий, которые должны случиться исключительно при успехе, но сами по себе не должны находиться под защитой try — так легче отличить код, который мог вызвать ошибку, от кода, который её обрабатывает. Блок finally выполняется всегда, независимо от того, случилось исключение или нет, поймал его except или пропустил мимо. Такой блок обычно закрывает файл, освобождает ресурс или печатает сообщение о завершении работы функции — действие, которое обязано произойти при любом исходе.
Порядок выполнения всех четырёх блоков
Сначала Python пытается выполнить try целиком. Если ошибки не было, следом выполняется else, а затем в любом случае finally. Если ошибка была и её поймал подходящий except, выполняется именно он, а затем снова finally. Если ошибка была, но подходящего except для неё не нашлось, программа всё равно сначала выполнит finally и только потом уже прервётся с исходным исключением — блок finally срабатывает даже тогда, когда обработать саму ошибку не удалось.
Цикл повторного запроса ввода
Одиночный try-except перехватывает ошибку один раз, но не заставляет пользователя ввести данные заново. Для этого try-except оборачивают в цикл while True. Внутри цикла программа запрашивает ввод и пытается преобразовать его в число. Если преобразование удалось, из цикла выходят командой break. Если возникло исключение ValueError, except печатает сообщение об ошибке, и цикл начинается заново — while True без условия выхода продолжает крутиться до тех пор, пока пользователь не введёт корректное число и программа не дойдёт до break.
- цикл while True запускает бесконечный опрос до тех пор, пока код внутри не остановит его явно
- try внутри цикла пытается превратить очередной ввод в число
- except ловит неудачную попытку, печатает подсказку и позволяет циклу начать новый круг
- break внутри try срабатывает только при удачном вводе и завершает цикл
Частые вопросы
Чем исключение отличается от обычной проверки условием if
Проверка if требует заранее предсказать, что может пойти не так, и написать условие для каждого случая. Исключение возникает автоматически там, где Python сам обнаружил ошибку во время выполнения, и его достаточно поймать один раз в подходящем месте, даже не зная заранее всех возможных причин сбоя.
Можно ли перехватить сразу несколько типов ошибок в одном блоке
Да, для этого типы перечисляют в круглых скобках через запятую после except. Такой блок сработает на любую ошибку из списка и выполнит для неё один и тот же код обработки.
Что будет, если ошибка возникла, а подходящего except нет
Программа завершится с трассировкой вызовов точно так же, как без всякого try — except перехватывает только те типы ошибок, которые в нём явно названы, а остальные пролетают мимо.
Обязательно ли писать else и finally
Нет, оба блока необязательны. Простейшая конструкция из try и except уже работает полностью, а else и finally добавляют только тогда, когда нужно разделить успешный путь выполнения и обязательную финальную часть.