Урок курса

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

Python для новичков

В прошлом уроке мы научились читать traceback: находить строку с ошибкой, определять её тип и понимать, что пошло не так. Теперь следующий шаг — не просто читать ошибку, а перехватывать её прямо во время выполнения, чтобы программа не падала.

Зачем нужна обработка исключений: идея try-except

Когда Python встречает операцию, которую не может выполнить — например, деление на ноль или преобразование строки в число — он генерирует исключение. Если это исключение никто не обрабатывает, интерпретатор останавливает программу и печатает traceback. Для скрипта, который запускают один раз и смотрят глазами, это терпимо. Для программы, которая взаимодействует с пользователем или обрабатывает внешние данные, — катастрофа: одна неожиданная строка ввода роняет всё.

Конструкция try-except решает эту проблему. Идея простая: ты заранее говоришь Python, что определённый кусок кода может упасть, и указываешь, что делать в этом случае.

Структурно это выглядит так:

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

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

  1. Python начинает выполнять инструкции внутри try — по одной, сверху вниз.

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

  3. Управление переходит в блок except, и Python выполняет то, что там написано.

  4. После except программа продолжает работу в обычном режиме — она не падает.

Если же внутри try всё прошло без исключений, блок except просто пропускается.

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

Обрати внимание: после except рекомендуется указывать конкретный тип исключения — ValueError, ZeroDivisionError и так далее. Почему это важно — разберём в следующей секции. Пока запомни саму механику: при ошибке внутри try Python немедленно переходит в except и продолжает работу после блока, не роняя программу.

Перехват конкретного типа исключения

Можно написать except: без указания типа — это работает, но это грубый инструмент. На практике тебе нужно знать не просто «что-то пошло не так», а именно что: пользователь ввёл не число или кто-то делит на ноль. Для этого после except указывают конкретный класс исключения:

except ValueError:
    ...

При такой записи блок сработает только если возникло именно ValueError. Любое другое исключение — ZeroDivisionError, TypeError, что угодно — пройдёт мимо этого except и либо будет поймано следующим обработчиком, либо уронит программу с traceback. Типизированный except — это фильтр, а не универсальный пылесос. Почему голый except: без типа опасен — разберём подробно в секции про типичные ошибки; пока сосредоточимся на том, как правильно указывать тип.

Пример 1: ValueError при вводе числа

int() вызывает ValueError, когда строка не является целым числом. Именно это происходит, если пользователь вводит "abc" или "12.5":

try:
    n = int(input("Введите целое число: "))
    print("Вы ввели:", n)
except ValueError:
    print("Ошибка: нужно ввести целое число, а не текст")

print("Программа продолжает работу")

Если пользователь вводит "abc" — int("abc") бросает ValueError, Python немедленно прыгает в except ValueError:, печатает сообщение, и программа продолжается. Строка print("Вы ввели:", n) при этом не выполняется вовсе — она осталась внутри try после точки падения.

Если пользователь вводит 42 — ValueError не возникает, except пропускается, оба print отрабатывают нормально.

Пример 2: ZeroDivisionError при делении

Деление на ноль — отдельный тип исключения, и для него нужен свой except:

a = 10
b = 0

try:
    result = a / b
    print("Результат:", result)
except ZeroDivisionError:
    print("Ошибка: делить на ноль нельзя")

print("Вычисление завершено")

При b = 0 строка a / b бросает ZeroDivisionError, блок except ZeroDivisionError: перехватывает его. При b = 5 исключения нет, результат печатается нормально, except не трогается.

Обрати внимание: except ValueError: не поймал бы ZeroDivisionError — и наоборот. Каждый except отвечает ровно за свой тип. Это и есть смысл типизированного перехвата: ты точно знаешь, какую именно ситуацию обрабатываешь, и пишешь осмысленное сообщение именно под неё.

Если нужно обработать оба типа в одном блоке try, можно написать два except подряд:

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

Python проверяет except-блоки сверху вниз и заходит в первый подходящий. Если пользователь вводит "abc" — срабатывает первый. Если вводит 0 — срабатывает второй. Это даёт разные, точные сообщения для разных ситуаций — именно то, что нужно пользователю.

Процедура написания try-except: пошаговый алгоритм

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

Шаг 1. Найди опасное место

Сначала спроси себя: какая конкретная строка может вызвать исключение? Это не всегда очевидно, но обычно подозреваемые — это:

  • вызов int() или float() на пользовательском вводе;
  • деление, где знаменатель может оказаться нулём;
  • обращение к внешним данным, формат которых заранее неизвестен.

Найди именно эту строку — не функцию целиком, не блок из десяти строк, а конкретную операцию.

Шаг 2. Оберни минимальный код в try

Поместить в try нужно ровно столько, сколько необходимо — только те строки, которые сами могут бросить исключение. Если опасные строки — это int(input(...)) и деление, то в try идут именно они:

try:
    n = int(input("Введите делитель: "))
    result = 100 / n

Здесь ровно две строки: первая может дать ValueError, вторая — ZeroDivisionError. print с результатом выносим наружу — он безопасен и не должен находиться под защитой.

Шаг 3. Напиши except с конкретным типом

После try — один или несколько блоков except, каждый с конкретным типом:

except ValueError:
    print("Ошибка: введите целое число")
except ZeroDivisionError:
    print("Ошибка: делитель не может быть нулём")

Каждое сообщение объясняет именно ту ситуацию, которую перехватывает. Пользователь видит понятный текст, а не Python traceback.

Шаг 4. Убедись, что код после блока продолжает работу

Поставь после всей конструкции строку, которая должна выполниться в любом случае — и проверь, что она действительно выполняется при обоих исходах:

result = None

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

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

print("Готово")  # выполняется всегда

Запусти программу дважды: один раз введи нормальное число, второй — что-нибудь неправильное. В обоих случаях print("Готово") должен появиться в выводе. Если не появляется — где-то внутри try осталось необработанное исключение другого типа.

Шаг 5. Перечитай try и убедись, что он не раздулся

Это финальная проверка: пройдись по строкам внутри try и спроси себя, может ли каждая из них вызвать исключение того типа, который ты обрабатываешь. Если какие-то строки там явно безопасны и не зависят от опасной операции — вынеси их наружу. Чем уже 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 — только тот минимальный код, который непосредственно может вызвать перехватываемое исключение. Всё остальное — снаружи.

Задача 1

Посмотрите на код:

try:
    x = int(input('Введите число: '))
    print(x * 2)
except ValueError:
    print('Некорректный ввод')

Пользователь вводит строку 'привет'. Что выведет программа?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Что произойдёт, если в блоке try возникнет исключение типа TypeError, а в except указан только ValueError?

try:
    result = 'цена: ' + 100
except ValueError:
    print('Поймано!')
Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Что выведет следующий код, если пользователь вводит строку '0'? Напишите вывод точно так, как он появится в терминале.

try:
    n = int(input('Введите число: '))
    print(100 / n)
except ValueError:
    print('Нужно ввести число')
except ZeroDivisionError:
    print('Ноль не подходит')
Без регистрации · результат не сохраняется

Задача 4

Исправь обработку двух типов ошибок в оплате

В стартовом коде есть функция check_payment(a_str, b_str), которая принимает две строки, преобразует их в целые числа и должна вернуть результат целочисленного деления a // b.

Сейчас в коде обработан только один тип ошибки, поэтому программа не умеет корректно реагировать на деление на ноль.

Исправьте функцию так, чтобы она:

  • возвращала a // b, если обе строки успешно преобразовались в числа и делитель не равен нулю;
  • возвращала строку Ошибка: некорректное число, если int() не смог преобразовать одну из строк;
  • возвращала строку Ошибка: деление на ноль, если делитель оказался равен 0.

После функции не меняйте общий смысл программы: внизу уже должны быть вызовы check_payment('18', '3'), check_payment('12', '0'), check_payment('x', '5').

Исправьте именно обработку исключений.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 5

Безопасный расчёт скорости по двум введённым значениям

Напишите программу safe_speed.py.

Пользователь вводит две строки по порядку: расстояние в километрах и время в часах.

Программа должна:

  • в блоке try преобразовать обе строки в целые числа;
  • вычислить скорость по формуле distance // time;
  • вывести результат;
  • если одна из строк не является целым числом, вывести Ошибка: некорректное число;
  • если время равно нулю, вывести Ошибка: деление на ноль.

После обработки ошибки программа должна завершаться без traceback.

Формат ввода: две строки, сначала расстояние, затем время.
Формат вывода: одно сообщение или одно число.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 6

Безопасное деление двух чисел из строк

Напишите функцию safe_int_divide(a_str, b_str), которая принимает две строки.

Функция должна:

  • внутри блока try преобразовать обе строки в целые числа через int();
  • выполнить целочисленное деление a // b и вернуть результат;
  • если при преобразовании возникла ошибка, перехватить ValueError и вернуть строку Ошибка: некорректное число;
  • если при делении возникла ошибка, перехватить ZeroDivisionError и вернуть строку Ошибка: деление на ноль.

Функция ничего не печатает, а только возвращает значение.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку