Модуль 8 · Исключения и файлы

Справочник по Python · модуль 8 · исключения и файлы

Глава 8.1

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 — то, что делать, если она произошла.

main.pyделение на введённое число
try:
    # код, который может вызвать исключение
    number = int(input("Введите целое число: "))
    result = 10 / number
except ValueError:
    # обработка ошибки преобразования
    print("Ошибка: пожалуйста, введите корректное целое число!")
except ZeroDivisionError:
    # обработка ошибки деления на ноль
    print("Ошибка: деление на ноль!")
Вывод при вводе «текст»программа не упала
Ошибка: пожалуйста, введите корректное целое число!

Здесь предусмотрены оба сценария: строка вместо числа даёт ValueError внутри int(), а ноль — ZeroDivisionError при делении.

§3Что делает except

Важно понимать механику, а не только синтаксис.

  1. Строки блока try выполняются как обычно, сверху вниз.
  2. Как только возникает исключение, остаток блока try пропускается — даже если там оставались строки.
  3. Python ищет подходящий except: тот, чей тип совпадает с типом ошибки.
  4. Найденный блок выполняется, и программа продолжается со строки после всей конструкции.
  5. Если подходящего except нет, исключение уходит дальше и программа останавливается.

Пункт 2 объясняет, почему в примере из §2 переменная result может остаться несозданной: если int() выбросил ошибку, до деления дело не дошло.

§4as: сам объект ошибки

Ключевое слово as связывает исключение с переменной — тогда доступно его сообщение.

main.pyпечатаем и своё пояснение, и текст ошибки
try:
    number = int("N/A")
except ValueError as e:
    print("Ошибка: неверный ввод!", e)
Выводпосле запятой — сообщение самого Python
Ошибка: неверный ввод! invalid literal for int() with base 10: 'N/A'

Сообщение из исключения полезно в логах: оно точно называет причину. Пользователю обычно показывают своё пояснение, а исходный текст записывают в журнал.

§5Несколько блоков except

Блоков может быть сколько угодно — сработает первый подходящий, остальные будут пропущены.

main.pyу каждой ошибки своё сообщение
try:
    number = int(input("Введите целое число: "))
    result = 10 / number
except ValueError as e:
    print("Ошибка: неверный ввод!", e)
except ZeroDivisionError as e:
    print("Ошибка: деление на ноль!", e)
Вывод при вводе «0»сработал второй блок
Ошибка: деление на ноль! division by zero

Раздельные блоки нужны, когда на разные ошибки реакция разная: одну можно исправить повторным вводом, другую — только сообщением администратору.

§6Один блок на несколько типов

Если реакция одинаковая, типы перечисляют в кортеже.

main.pyобе ошибки обрабатываются вместе
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. После выхода из него имя удаляется.

main.pyобращение к e после блока
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 не было исключений.

main.pyрезультат печатается только при успехе
try:
    number = int(input("Введите целое число: "))
    result = 10 / number
except ValueError as e:
    print("Ошибка: неверный ввод!", e)
except ZeroDivisionError as e:
    print("Ошибка: деление на ноль!", e)
else:
    print("Результат деления:", result)
Вывод при вводе «2»ошибок не было
Результат деления: 5.0

Возникает вопрос: зачем else, если эту строку можно было оставить внутри try? Затем, что try должен содержать только то, что может упасть. Если print() оставить внутри, а он сам вызовет ошибку, её случайно перехватит блок except, написанный совсем для другого случая.

§10Блок finally

finally выполняется в любом случае — и при успехе, и при ошибке, и даже если ошибка не была перехвачена.

main.pyполная конструкция из четырёх блоков
try:
    number = int(input("Введите целое число: "))
    result = 10 / number
except ValueError as e:
    print("Ошибка: неверный ввод!", e)
except ZeroDivisionError as e:
    print("Ошибка: деление на ноль!", e)
else:
    print("Результат деления:", result)
finally:
    print("Операция завершена.")
Вывод при вводе «0»finally сработал после except
Ошибка: деление на ноль! division by zero
Операция завершена.

Назначение блока — освободить ресурсы: закрыть файл, разорвать соединение с базой, убрать временные данные. Это должно произойти независимо от того, чем закончилась работа.

§11Полный порядок выполнения

Что случилось в tryexceptelsefinally
ошибок не былопропускаетсявыполняетсявыполняется
ошибка перехваченавыполняетсяпропускаетсявыполняется
ошибка не перехваченапропускаетсяпропускаетсявыполняется, затем программа падает

Обязателен только try и хотя бы один из except или finally. Блок else без except не бывает.

§12finally при непойманной ошибке

Третья строка таблицы — самая показательная. Даже когда обработчика нет и программа обречена упасть, finally успевает отработать.

main.pyexcept отсутствует вовсе
try:
    raise ValueError("наружу")
finally:
    print("finally всё равно выполнен")
Вывод и ошибкасначала уборка, потом падение
finally всё равно выполнен
ValueError: наружу

Именно на этом свойстве построен контекстный менеджер with из главы 8.5: он гарантирует закрытие файла даже при аварии. Оператор raise разбирается в главе 8.2.

§13Что не стоит ловить

Обработка ошибок легко превращается в их сокрытие. Три записи, которых стоит избегать.

ЗаписьЧем плоха
except: без типаперехватывает вообще всё, включая прерывание с клавиатуры
except Exception: passошибка исчезает бесследно, причина не видна
огромный блок tryнепонятно, какая именно строка упала

Правило: ловить конкретный тип и минимальный участок кода. Обработчик должен знать, что именно он чинит; всё остальное лучше пропустить наверх, чтобы ошибка стала видна.

§14Практика: надёжный ввод

Классическое применение — не дать программе упасть на неверном вводе, а попросить ввести заново.

main.pyцикл повторяется до корректного значения
while True:
    try:
        age = int(input("Введите возраст: "))
    except ValueError:
        print("Нужно целое число, попробуйте ещё раз.")
    else:
        break

print("Возраст:", age)
Вывод при вводе «abc», затем «30»цикл вышел после успеха
Нужно целое число, попробуйте ещё раз.
Возраст: 30

Здесь else работает по назначению: break должен сработать только при успешном преобразовании. Внутри try его ставить не стоит — блок try держат минимальным.

§15Частые ошибки

Что написаноЧто произойдётКак правильно
except Exception перед except ValueErrorвторой блок недостижимот частного к общему
except ValueError, ZeroDivisionError:SyntaxErrorзаключить в скобки как кортеж
обращение к e после блока exceptNameErrorсохранить 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 выполняется всегда — на нём построено надёжное освобождение ресурсов.

Глава 8.1 · Исключения: try, except, else, finally · Справочник по Python Автор: Макаров Максим Николаевич