Плановая обработка ошибок: try, except и собственные исключения
💻 Информатика · 9 класс
Ошибка не должна рушить программу неожиданно. Разберём механику try/except, почему except должен быть конкретным, как поднимать свои ValueError и зачем нужен finally.

Два вида ошибок
Пропущенная скобка или двоеточие могут вызвать SyntaxError при разборе исходного файла: его обычное выполнение не начнётся. Сам SyntaxError тоже является исключением; например, его можно перехватить при вызове compile для отдельной строки исходного кода. Во время выполнения встречаются и другие исключения: деление на ноль (ZeroDivisionError), обращение по несуществующему ключу (KeyError), плохое преобразование типа (ValueError). Исключения можно перехватывать и обрабатывать — этим мы и займёмся.
Как работает try/except
Блок try выполняется как обычно. Если внутри возникает исключение, оставшаяся часть try пропускается, и управление переходит к первому блоку except, чей тип подходит. Если подходящего обработчика здесь нет, исключение передаётся внешнему вызывающему коду; если его нигде не перехватят, выполнение завершится с ошибкой. При обычном управлении Python блок finally выполняется при выходе из try — и при исключении, и без него; в него помещают очистку. Это не гарантия при аварийном завершении или принудительном уничтожении процесса.
Пример 1. Строгий разбор числа
Напишем функцию, которая принимает строку с положительным целым числом и отклоняет неправильный формат или неположительное значение с понятным сообщением.
def parse_positive_int(text):
number = int(text) # ValueError для 'abc', '3.5', ''
if number <= 0:
raise ValueError(f'ожидалось число больше 0, получено {number}')
return numberЗдесь два источника ValueError: встроенное преобразование int(text) и наша проверка через raise. Одна функция отвечает за полный цикл проверки: вызывающий код получает либо число, либо ValueError — и может решить, что делать. Проверьте границы: '7' вернёт 7, а '0', '-2', 'abc', '3.5' и пустая строка выбросят ValueError.
Пример 2. Кошелёк с правилами
Учебный класс Wallet предполагает целые денежные единицы и числовые аргументы. Это не реализация банковского счёта. Он показывает плановую проверку данных: ошибочные операции отклоняются до того, как испортят состояние.
class Wallet:
def __init__(self, balance):
if balance < 0:
raise ValueError('начальный баланс не может быть отрицательным')
self.balance = balance
def spend(self, amount):
if amount <= 0:
raise ValueError('сумма покупки должна быть положительной')
if amount > self.balance:
raise ValueError('недостаточно средств')
self.balance -= amount
return self.balanceКошелёк(100), покупка на 30 вернёт 70. Попытка потратить 0, -10 или 60 при балансе 50 выбросит ValueError, а баланс останется прежним — неудачная операция не меняет данные.
Правило конкретного except
Официальный учебник рекомендует перечислять конкретные типы исключений и не перехватывать всё подряд: пустой except скроет и опечатку в коде тоже. Хороший стиль — ловить ValueError там, где ждёшь плохую строку, KeyError — там, где обращаешься к словарю. Если обработчиков несколько, помните: срабатывает первый подходящий, остальные пропускаются, поэтому ставьте частные типы раньше общих.
Пример 3. Мягкий режим: пропускаем плохие строки
def safe_sum(texts):
total = 0
skipped = []
for text in texts:
try:
total += int(text)
except ValueError:
skipped.append(text)
return total, skippedsafe_sum(['10', 'x', '5', 'y']) вернёт (15, ['x', 'y']). Это альтернатива строгому parse_positive_int: одна и та же ошибка может быть фатальной в одной программе и безобидной в другой — выбор делает программист.
Обработчик, else и очистка
try:
result = parse_positive_int('7')
except ValueError:
print('Неверное число')
else:
print(result)
finally:
print('Проверка закончена')Получим 7 и строку «Проверка закончена». Если заменить '7' на 'x', выполнятся обработчик и finally. Else выполняется, когда try завершился без исключения. Return в finally может скрыть исходное исключение, поэтому такую очистку не используют для подмены результата.
Проверь себя
- Что произойдёт в parse_positive_int('3.5')? Ответ: ValueError от int() — строка с точкой не является записью целого числа.
- Когда выполнится finally, если в try ошибки не было? Ответ: тоже выполнится — finally отработает при обычном выходе из try.
- Почему except без типа опасен? Ответ: он перехватывает любые исключения, включая неожиданные ошибки программы, и мешает их заметить.
- Wallet(50); spend(60) — каким будет баланс после исключения? Ответ: 50, потому что raise сработал до изменения поля.
Кратко о главном
Исключения — ошибки времени выполнения, которые можно перехватывать. try/except ловит конкретные типы, finally выполняется при обычном выходе из try, raise позволяет отклонять плохие данные самому. Перехватывайте только те исключения, которые готовы обработать.