Справочник по Python · модуль 8 · исключения и файлы
Глава 8.1
Исключения: try,
except, else, finally
Исключение прерывает нормальный ход программы. Блок try отмечает участок, где ошибка возможна, except её обрабатывает, else выполняется при успехе, а finally — в любом случае. Вместе они превращают аварийную остановку в предусмотренный сценарий.
- Категория
- Обработка ошибок
- Уровень
- Базовый → средний
- Связанные темы
- Типы ошибок, ввод данных, файлы
- Зачем это знать
- Не давать программе падать на предсказуемых ошибках
§1Что такое исключение
Исключение — событие во время выполнения программы, которое сообщает об ошибке и прерывает нормальный ход выполнения. Такие события встречались во всех предыдущих модулях.
| Причина | Исключение | Где разбиралось |
|---|---|---|
| деление на ноль | ZeroDivisionError | глава 1.8 |
| обращение к несуществующему элементу | IndexError | глава 3.1 |
| обращение к несуществующему ключу | KeyError | глава 4.2 |
| неверный тип данных | TypeError | глава 1.2 |
| верный тип, недопустимое значение | ValueError | глава 5.1 |
| ошибка ввода-вывода | OSError | глава 8.4 |
До сих пор любое из них означало конец работы: программа печатала трейсбек и останавливалась. Эта глава про то, как перехватить событие и продолжить.
§2try и except
Основная конструкция состоит из двух блоков: в try помещают код, который может вызвать ошибку, в except — то, что делать, если она произошла.
try: # код, который может вызвать исключение number = int(input("Введите целое число: ")) result = 10 / number except ValueError: # обработка ошибки преобразования print("Ошибка: пожалуйста, введите корректное целое число!") except ZeroDivisionError: # обработка ошибки деления на ноль print("Ошибка: деление на ноль!")
Ошибка: пожалуйста, введите корректное целое число!
Здесь предусмотрены оба сценария: строка вместо числа даёт ValueError внутри int(), а ноль — ZeroDivisionError при делении.
§3Что делает except
Важно понимать механику, а не только синтаксис.
- Строки блока
tryвыполняются как обычно, сверху вниз. - Как только возникает исключение, остаток блока
tryпропускается — даже если там оставались строки. - Python ищет подходящий
except: тот, чей тип совпадает с типом ошибки. - Найденный блок выполняется, и программа продолжается со строки после всей конструкции.
- Если подходящего
exceptнет, исключение уходит дальше и программа останавливается.
Пункт 2 объясняет, почему в примере из §2 переменная result может остаться несозданной: если int() выбросил ошибку, до деления дело не дошло.
§4as: сам объект ошибки
Ключевое слово as связывает исключение с переменной — тогда доступно его сообщение.
try: number = int("N/A") except ValueError as e: print("Ошибка: неверный ввод!", e)
Ошибка: неверный ввод! invalid literal for int() with base 10: 'N/A'
Сообщение из исключения полезно в логах: оно точно называет причину. Пользователю обычно показывают своё пояснение, а исходный текст записывают в журнал.
§5Несколько блоков except
Блоков может быть сколько угодно — сработает первый подходящий, остальные будут пропущены.
try: number = int(input("Введите целое число: ")) result = 10 / number except ValueError as e: print("Ошибка: неверный ввод!", e) except ZeroDivisionError as e: print("Ошибка: деление на ноль!", e)
Ошибка: деление на ноль! division by zero
Раздельные блоки нужны, когда на разные ошибки реакция разная: одну можно исправить повторным вводом, другую — только сообщением администратору.
§6Один блок на несколько типов
Если реакция одинаковая, типы перечисляют в кортеже.
try: result = 10 / 0 except (ValueError, ZeroDivisionError) as e: print("Произошла ошибка:", e)
Произошла ошибка: division by zero
Скобки обязательны — это кортеж из главы 4.1. Без них запись будет синтаксически неверной.
§7Порядок блоков важен
Раз срабатывает первый подходящий блок, порядок решает исход. Общий тип, поставленный первым, перехватит всё и не даст сработать частным.
except ValueError: сначала частный случай ✓ except Exception: потом общий except Exception: общий перехватит всё except ValueError: сюда управление не попадёт никогда ✗
Правило: от частного к общему. Почему Exception перехватывает и ValueError, и почти всё остальное, объясняет глава 8.3 — там разбирается иерархия типов исключений.
§8Область видимости переменной
Переменная из as живёт только внутри своего блока except. После выхода из него имя удаляется.
try: int("N/A") except ValueError as e: print("Failed:", e) print(e)
Failed: invalid literal for int() with base 10: 'N/A' NameError: name 'e' is not defined
Это сделано намеренно: объект исключения хранит ссылки на весь контекст ошибки, и Python освобождает их сразу. Если сообщение нужно дальше, его сохраняют в свою переменную: message = str(e).
§9Блок else
else выполняется, только если в try не было исключений.
try: number = int(input("Введите целое число: ")) result = 10 / number except ValueError as e: print("Ошибка: неверный ввод!", e) except ZeroDivisionError as e: print("Ошибка: деление на ноль!", e) else: print("Результат деления:", result)
Результат деления: 5.0
Возникает вопрос: зачем else, если эту строку можно было оставить внутри try? Затем, что try должен содержать только то, что может упасть. Если print() оставить внутри, а он сам вызовет ошибку, её случайно перехватит блок except, написанный совсем для другого случая.
§10Блок finally
finally выполняется в любом случае — и при успехе, и при ошибке, и даже если ошибка не была перехвачена.
try: number = int(input("Введите целое число: ")) result = 10 / number except ValueError as e: print("Ошибка: неверный ввод!", e) except ZeroDivisionError as e: print("Ошибка: деление на ноль!", e) else: print("Результат деления:", result) finally: print("Операция завершена.")
Ошибка: деление на ноль! division by zero Операция завершена.
Назначение блока — освободить ресурсы: закрыть файл, разорвать соединение с базой, убрать временные данные. Это должно произойти независимо от того, чем закончилась работа.
§11Полный порядок выполнения
| Что случилось в try | except | else | finally |
|---|---|---|---|
| ошибок не было | пропускается | выполняется | выполняется |
| ошибка перехвачена | выполняется | пропускается | выполняется |
| ошибка не перехвачена | пропускается | пропускается | выполняется, затем программа падает |
Обязателен только try и хотя бы один из except или finally. Блок else без except не бывает.
§12finally при непойманной ошибке
Третья строка таблицы — самая показательная. Даже когда обработчика нет и программа обречена упасть, finally успевает отработать.
try: raise ValueError("наружу") finally: print("finally всё равно выполнен")
finally всё равно выполнен ValueError: наружу
Именно на этом свойстве построен контекстный менеджер with из главы 8.5: он гарантирует закрытие файла даже при аварии. Оператор raise разбирается в главе 8.2.
§13Что не стоит ловить
Обработка ошибок легко превращается в их сокрытие. Три записи, которых стоит избегать.
| Запись | Чем плоха |
|---|---|
except: без типа | перехватывает вообще всё, включая прерывание с клавиатуры |
except Exception: pass | ошибка исчезает бесследно, причина не видна |
огромный блок try | непонятно, какая именно строка упала |
Правило: ловить конкретный тип и минимальный участок кода. Обработчик должен знать, что именно он чинит; всё остальное лучше пропустить наверх, чтобы ошибка стала видна.
§14Практика: надёжный ввод
Классическое применение — не дать программе упасть на неверном вводе, а попросить ввести заново.
while True: try: age = int(input("Введите возраст: ")) except ValueError: print("Нужно целое число, попробуйте ещё раз.") else: break print("Возраст:", age)
Нужно целое число, попробуйте ещё раз. Возраст: 30
Здесь else работает по назначению: break должен сработать только при успешном преобразовании. Внутри try его ставить не стоит — блок try держат минимальным.
§15Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
except Exception перед except ValueError | второй блок недостижим | от частного к общему |
except ValueError, ZeroDivisionError: | SyntaxError | заключить в скобки как кортеж |
обращение к e после блока except | NameError | сохранить str(e) в свою переменную |
except Exception: pass | ошибка исчезает бесследно | хотя бы записать её в лог |
весь код программы внутри одного try | непонятно, что именно упало | оборачивать минимальный участок |
полезный код в try вместо else | его ошибки перехватит чужой обработчик | перенести в else |
ждать, что finally отменит ошибку | исключение всё равно уйдёт дальше | для перехвата нужен except |
§16Проверьте себя
Что происходит с остатком блока try после ошибки?
Он пропускается целиком: управление сразу переходит к поиску подходящего except.
Зачем нужен as e?
Чтобы получить сам объект исключения и его сообщение — это точное описание причины, полезное в логах.
Почему порядок блоков except важен?
Срабатывает первый подходящий. Общий тип, поставленный раньше частного, перехватит всё и сделает следующие блоки недостижимыми.
Чем else лучше, чем те же строки внутри try?
В try должно быть только то, что может упасть. Иначе ошибка из «мирного» кода случайно попадёт в обработчик, написанный для другого случая.
Выполнится ли finally, если исключение никто не перехватил?
Да. Он отработает, и только потом программа остановится.
Чем плоха запись except Exception: pass?
Ошибка исчезает бесследно: программа продолжает работу с неверными данными, а причину найти невозможно.
§17Шпаргалка
try: ... except ValueError: ...except ValueError as e:except (A, B) as e:else:finally:частное → общееmessage = str(e)Итог главы
Исключение прерывает выполнение, и остаток блока try пропускается. except перехватывает ошибку по типу, срабатывая первым подходящим блоком, — поэтому частные типы пишут раньше общих. as даёт доступ к объекту ошибки, но только внутри своего блока. else отделяет код, который должен идти при успехе, и позволяет держать try минимальным, а finally выполняется всегда — на нём построено надёжное освобождение ресурсов.