Урок курса
Обработка ошибок ввода и отсутствия файла
Python для новичковВ прошлом уроке мы разобрали базовую механику try-except: как обернуть опасный код, как указать конкретный тип исключения и почему голый except — плохая идея. Сейчас этот инструмент нужно применить к двум реальным сценариям — некорректный ввод пользователя и попытка открыть файл, которого не существует.
Контекст урока: try-except и базовые исключения
Прежде чем переходить к конкретным паттернам, важно понять, с какими исключениями будем работать в этом уроке.
Первое — ValueError. Оно возникает, когда вы пытаетесь преобразовать строку в число, а строка не содержит допустимого значения. Например, int('abc') или int('') — оба вызова завершатся ValueError. Это исключение вы уже видели раньше.
Второе — FileNotFoundError. Оно появляется в тот момент, когда Python выполняет open() в режиме чтения ('r'), а файл с указанным именем не существует на диске. Технически FileNotFoundError является подклассом OSError — общего класса для ошибок операционной системы. Это значит, что его можно перехватить и через except OSError:, но точнее и понятнее писать именно except FileNotFoundError:.
Почему open() бросает исключение? Потому что Python пытается обратиться к файловой системе, получает отказ от ОС и сигнализирует об этом через исключение. Никакой «тихой» обработки по умолчанию нет — если не перехватить FileNotFoundError, программа упадёт с трейсбэком.
Важно понимать: FileNotFoundError актуален прежде всего при открытии на чтение ('r'), когда файл обязан уже существовать. Если вы открываете файл на запись ('w') и директория существует, Python создаст файл сам — FileNotFoundError не возникнет. Поэтому в этом уроке фокус на режиме чтения.
Эти два исключения покрывают разные точки программы: ValueError — при обработке пользовательского ввода, FileNotFoundError — при работе с файловой системой. В обоих случаях инструмент одинаков: try-except с явным указанием типа.
Перехват FileNotFoundError при чтении файла
Теперь посмотрим, как это выглядит на практике. Задача простая: открыть файл на чтение, но сделать это так, чтобы программа не падала, если файла нет.
Конструкция with open(...) as f уже знакома — она сама закрывает файл по выходу из блока. Здесь она остаётся без изменений, только оборачивается в try:
try:
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
print('Файл прочитан, символов:', len(content))
except FileNotFoundError:
print('Ошибка: файл data.txt не найден')
Что здесь происходит: open('data.txt', 'r', ...) — это та самая «опасная» строка. Если data.txt отсутствует на диске, Python не доберётся до f.read() — исключение возникнет уже при вызове open(). Блок except FileNotFoundError: ловит именно этот момент и выводит понятное сообщение вместо трейсбэка.
Обратите внимание: print с длиной контента стоит внутри try, после with-блока. Это правильно — он выполнится только если файл открылся успешно. Если бы он стоял после всего try-except, он выполнялся бы даже при ошибке, что было бы логически некорректно.
Ещё один момент: except FileNotFoundError: не перехватывает другие ошибки файловой системы — например, PermissionError (когда файл есть, но нет прав на чтение). Это нормально: мы перехватываем ровно ту ситуацию, которую ожидаем и умеем обработать. Всё остальное пусть всплывает как обычно.
Паттерн цикла повторного запроса при некорректном вводе
Одиночный try-except хорош, когда ошибка некритична и программа может продолжить работу без повторной попытки. Но с пользовательским вводом ситуация другая: если человек ввёл не число, нужно не просто вывести сообщение и двигаться дальше — нужно спросить снова. Именно для этого существует паттерн «бесконечный цикл с выходом по успеху».
Идея простая: запускаем while True — цикл, который в норме никогда не остановится сам. Внутри помещаем try-except. Если преобразование прошло успешно, вызываем break — и только тогда. Если возникло исключение, except выводит сообщение, break не вызывается, и цикл идёт на следующую итерацию: пользователь снова видит приглашение ввода.
while True:
try:
n = int(input('Введите целое число: '))
break
except ValueError:
print('Это не число. Попробуйте ещё раз.')
print('Вы ввели:', n)
Проследим путь выполнения на двух сценариях.
Сценарий 1 — пользователь вводит 'abc':
int('abc')бросаетValueError- управление переходит в
except, печатается сообщение breakне достигается- цикл начинается заново
Сценарий 2 — пользователь вводит '42':
int('42')возвращает42, переменнаяnполучает значение- следующая строка —
break— выполняется - цикл завершается
printпосле цикла выводит значениеn
Ключевой момент этого паттерна — break является условием выхода, а не просто завершением. Цикл работает не «пока что-то верно», а «пока не получен корректный ввод». Это архитектурное решение: вместо условия в заголовке while логика остановки вынесена внутрь try.
Такой подход хорошо масштабируется. Можно добавить ограничение диапазона значений прямо в блок try, и оно тоже будет под защитой цикла:
while True:
try:
age = int(input('Введите ваш возраст (1–120): '))
if age < 1 or age > 120:
print('Возраст вне допустимого диапазона.')
continue
break
except ValueError:
print('Введите целое число.')
print('Возраст принят:', age)
Здесь continue возвращает управление в начало цикла, если число корректное по типу, но не по диапазону. break вызывается только когда оба условия выполнены. Это не усложнение ради усложнения — это демонстрация того, что паттерн гибкий: к нему можно добавлять проверки без изменения общей структуры.
Правильное размещение break и пошаговая реализация цикла
Самая частая ошибка при написании цикла повторного запроса — break стоит не там, где нужно. Выглядит это так:
# Неправильно
while True:
try:
n = int(input('Введите число: '))
except ValueError:
print('Это не число. Попробуйте ещё раз.')
break # <-- здесь
break здесь стоит после блока try-except, то есть на одном уровне отступа с try. Что происходит при выполнении? Независимо от того, было исключение или нет, после обработки try-except Python доходит до break и выходит из цикла. Пользователь получает ровно один шанс — и если он ввёл 'abc', программа всё равно завершает цикл, переменная n не определена, и дальнейший код упадёт с NameError.
Правильный вариант — break внутри try, после успешного преобразования:
# Правильно
while True:
try:
n = int(input('Введите число: '))
break # выполняется только если int() не бросил исключение
except ValueError:
print('Это не число. Попробуйте ещё раз.')
print('Принято:', n)
Когда int(input(...)) бросает ValueError, выполнение немедленно уходит в except — строка с break просто не достигается. Цикл продолжается. Когда преобразование проходит успешно, break достигается и цикл завершается. Именно так позиция одной строки определяет, работает паттерн или нет.
Чтобы каждый раз не думать «куда поставить break», удобно держать в голове чёткую последовательность шагов.
Шаг 1.
Написать while True: — это каркас, который держит цикл активным.
Шаг 2.
Внутри сразу написать try: — весь опасный код пойдёт сюда.
Шаг 3.
В блоке try разместить вызов int(input(...)) или другое преобразование, которое может бросить исключение.
Шаг 4.
Следующей строкой после преобразования — break. Физически: сразу под строкой с int(input(...)), на том же уровне отступа внутри try.
Шаг 5.
Написать except ValueError: и внутри — сообщение пользователю. break здесь не ставить.
В итоге структура всегда выглядит так:
while True:
try:
# шаг 3: опасная операция
n = int(input('Введите целое число: '))
# шаг 4: break сразу после успеха
break
except ValueError:
# шаг 5: только сообщение, без break
print('Введите целое число, не текст.')
Если нужна дополнительная проверка значения, она добавляется между преобразованием и break — как показано в предыдущей секции. Структура при этом не меняется: break по-прежнему остаётся последней точкой в try, которой программа достигает только при полном успехе.
Запомнить порядок просто: try → операция → break → except → сообщение. Всё остальное — вариации внутри этого каркаса.
Попробуйте решить
Что выведет программа, если файл report.txt не существует?
try:
with open('report.txt', 'r', encoding='utf-8') as f:
print(f.read())
except FileNotFoundError:
print('Файл не найден')
Продолжить с проверкой и прогрессом
Откройте интерактивный раннер с заданиями урока.
