Ошибки и отладка

Обработка исключений через try-except

Содержание курса

Типичные ошибки при использовании try-except

Когда знаешь, как работает try-except, легко допустить два антипаттерна, которые не ломают программу сразу, но делают её опасной и трудноотлаживаемой.

Антипаттерн 1: except без указания типа

Выглядит безобидно:

try:
    n = int(input("Введите число: "))
    result = 100 / n
    print("Результат:", result)
except:
    print("Что-то пошло не так")

Проблема в том, что голый except: перехватывает буквально всё — включая исключения, о которых ты даже не подозревал. Предположим, внутри try есть опечатка в имени переменной: Python бросит NameError, и твой except молча его проглотит. В выводе появится «Что-то пошло не так» вместо нормального traceback, и ты потратишь час, выясняя, почему программа ведёт себя не так, как ожидалось.

Ещё хуже: except: ловит даже KeyboardInterrupt — то есть, если пользователь нажмёт Ctrl+C, чтобы остановить программу, она не остановится. Это уже не просто неудобство.

Исправление — всегда указывай конкретный тип и держи в try только опасные строки:

result = None

try:
    n = int(input("Введите число: "))
    result = 100 / n
except ValueError:
    print("Ошибка: введите целое число")
except ZeroDivisionError:
    print("Ошибка: делить на ноль нельзя")

if result is not None:
    print("Результат:", result)

Теперь любое неожиданное исключение — NameError, TypeError, что угодно — не проглатывается, а выдаёт traceback. Это именно то, что нужно при разработке: видеть реальные ошибки, а не прятать их.

Антипаттерн 2: слишком широкий блок try

Вторая ловушка — запихнуть в try большой кусок кода:

try:
    print("Начинаем вычисление")   # безопасная строка — зря в try
    n = int(input("Введите делитель: "))
    result = 100 / n
    print("Результат:", result)    # не выполнится при ошибке
    print("Всё готово")            # не выполнится при ошибке
except ValueError:
    print("Ошибка: введите целое число")

Если int(input(...)) бросает ValueError, Python немедленно прыгает в except — и все строки после проблемной внутри try не выполняются. Оба print в конце просто пропускаются. Чем больше try, тем труднее понять, откуда именно прилетело исключение — особенно если в блоке несколько мест, которые теоретически могут упасть с одним и тем же типом ошибки.

Исправление — вынести безопасные строки наружу, оставив в try только то, что действительно может бросить исключение:

result = None  # объявляем заранее — на случай, если try не завершится успешно

print("Начинаем вычисление")   # ← вынесли: не может вызвать исключение

try:
    n = int(input("Введите делитель: "))
    result = 100 / n
except ValueError:
    print("Ошибка: введите целое число")
except ZeroDivisionError:
    print("Ошибка: делить на ноль нельзя")

if result is not None:
    print("Результат:", result)  # ← вынесли; выполняется только при успехе

print("Всё готово")  # ← выполняется всегда, при любом исходе

Обрати внимание на result = None перед блоком: если внутри try возникнет исключение, переменная result не получит значения от деления. Без предварительного объявления строка print("Результат:", result) после блока вызвала бы NameError — программа снова упала бы, только уже по другой причине. Именно поэтому переменные, которые заполняются внутри try, нужно объявлять с безопасным начальным значением до блока.

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