Обработка исключений в Python

Программа спрашивает у пользователя возраст и прибавляет год:

Python 3.13
age = int(input("Сколько вам лет? "))
print("Через год будет", age + 1)

Пока вводят число, всё работает. Но стоит пользователю набрать «сорок» вместо 40, и int() не сможет превратить это в число: программа аварийно завершится с ошибкой ValueError, а строка с print уже не выполнится. Один неверный ввод роняет всю программу.

Обработка исключений нужна как раз для таких моментов: перехватить ошибку, среагировать и продолжить работу, вместо того чтобы падать при первой же неожиданности. Мы дважды откладывали этот разговор — в уроках про текстовые файлы и инкапсуляцию. Пришло время.

Блок try-except

Рискованный код кладут в try, реакцию на ошибку — в except:

Python 3.13
try:
    # код, который может упасть
except ТипИсключения:
    # что делать, если упал

Вот как это выглядит на настоящей ошибке:

Python 3.13
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Ошибка: деление на ноль!")
Ошибка: деление на ноль!

Программа не упала: Python дошёл до деления, увидел ошибку, перепрыгнул в except и продолжил работу.

Разные ошибки — разные реакции

В одном try может падать разное, и на каждый тип исключения заводится свой except. Python проверяет их по порядку и выполняет первый подходящий:

Python 3.13
file_name = "data.txt"

try:
    file = open(file_name, "r")
    number = int(file.readline().strip())
    print(f"Результат: {100 / number}")
except FileNotFoundError:
    print(f"Файл {file_name} не найден")
except ValueError:
    print("В файле не число")
except ZeroDivisionError:
    print("В файле ноль — делить нельзя")
Файл data.txt не найден

Если реакция на разные ошибки одна и та же, типы перечисляют в скобках через запятую:

Python 3.13
try:
    value = int("abc")
except (ValueError, ZeroDivisionError):
    print("Произошла ошибка в вычислениях")
Произошла ошибка в вычислениях

Блоки else и finally

else выполняется, только если в try ошибок не было:

Python 3.13
try:
    number = int("42")
except ValueError:
    print("Это не число")
else:
    print(f"Успешно! Число: {number}")
Успешно! Число: 42

finally выполняется всегда — была ошибка или нет:

Python 3.13
f = open("example.txt", "w")
try:
    f.write("Привет, мир!")
finally:
    f.close()   # выполнится, даже если запись упадёт
    print("Файл закрыт")
Файл закрыт

Обратите внимание: open() стоит до try. Если поместить его внутрь и файл не откроется, переменной f просто не будет, и f.close() в finally сам упадёт. Именно эту возню автоматизирует оператор with из главы про файлы — он закрывает файл за вас.

В finally кладут то, что должно случиться в любом случае, — чаще всего освобождение ресурсов вроде открытых файлов.

Информация об ошибке: as

Через as можно получить сам объект исключения — например, чтобы показать его сообщение:

Python 3.13
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Сообщение: {e}")
Сообщение: division by zero

Свои ошибки: raise

Оператор raise поднимает исключение вручную. Вы уже видели его в уроке про инкапсуляцию: там сеттер отказывался сохранять отрицательный баланс. Устроено это так:

Python 3.13
def check_age(age):
    if age < 0:
        raise ValueError("Возраст не может быть отрицательным")
    print(f"Возраст {age} принят")

try:
    check_age(-5)
except ValueError as e:
    print(f"Ошибка: {e}")
Ошибка: Возраст не может быть отрицательным

Не ловите всё подряд

Голый except: без типа перехватывает любые ошибки — в том числе те, о которых вы не подозревали, например опечатку в имени переменной. Программа «работает», а настоящая проблема молчит:

Python 3.13
# Плохо: непонятно, что именно случилось
try:
    number = int("abc")
except:
    print("Какая-то ошибка")
Какая-то ошибка
# Хорошо: ловим ровно то, что ожидаем
try:
    number = int("abc")
except ValueError:
    print("Неверный формат числа")
Неверный формат числа

Проверка понимания

Какой блок выполнится всегда, независимо от того, произошло исключение или нет?

В следующем уроке — декораторы. Вы уже встречали их дважды: @property в инкапсуляции и @abstractmethod в полиморфизме. Пришло время разобрать, как строка с @ устроена изнутри.