Skip to content

Глава 11. Ошибки и исключения: `try` / `except`

Задача главы: не давать одной кривой строке в выгрузке обрушить весь отчёт — перехватывать ошибки в нужном месте, считать отбракованные строки и продолжать обработку остальных.


11.1 try / except: перехват ValueError на приведении типов

Зачем это нужно. Глава Файлы и CSV закончилась предупреждением: одна испорченная строка в выгрузке — например, пустое значение вместо балла — валит int() исключением и обрывает весь скрипт, даже если остальные тысяча строк в полном порядке. try / except перехватывает ошибку в конкретном месте и позволяет решить, что делать дальше, вместо того чтобы просто падать.

// PYTHON_EXECUTOR (Pyodide Wasm)
Не удалось привести к числу: 'н/д'

Python выполняет блок try; если внутри возникает исключение указанного типа (ValueError), выполнение сразу передаётся в except, а не прерывает скрипт. Если исключения не произошло, блок except просто не выполняется.

⚠️ Подводный камень

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

// PYTHON_EXECUTOR (Pyodide Wasm)
Что-то пошло не так с данными
0
ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«Голый except: перехватывает любое исключение»Настоящая причина падения — NameError из-за опечатки scors — исчезла за общим сообщением «что-то пошло не так с данными». Баг в коде выглядит как проблема с данными и может остаться незамеченным надолго.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
Traceback (most recent call last):
File "report.py", line 4, in <module>
for score in scors:
NameError: name 'scors' is not defined

Пояснение логики: except ValueError не перехватывает NameError — она проходит насквозь и падает обычным traceback’ом, как и должна упасть настоящая ошибка в коде. Указывать конкретный тип исключения — не формальность, а способ не спрятать от себя баг под видом «плохих данных».


11.2 Несколько ветвей except, as error, «сколько строк отбраковано»

Зачем это нужно. У выгрузки бывает больше одного способа сломаться: не только текст вместо числа, но и, например, ученик без единой сдачи — с этим сценарием глава Функции уже сталкивалась: calculate_average() на пустом списке падает с ZeroDivisionError, а не с ValueError. Несколько ветвей except позволяют обработать каждый случай по-своему и посчитать, сколько строк в итоге пришлось отбраковать.

// PYTHON_EXECUTOR (Pyodide Wasm)
Иванов Иван: 71.7
Смирнова Ольга: пропущен (division by zero)
Отбраковано строк: 1

except ZeroDivisionError as error кладёт объект исключения в error — из него можно вытащить текст сообщения, как в примере выше, а не просто узнать, что что-то произошло.

⚠️ Подводный камень

Порядок веток важен: except Exception — самый широкий тип, перехватывающий почти всё. Если поставить его первым, более конкретные ветки ниже никогда не выполнятся.

// PYTHON_EXECUTOR (Pyodide Wasm)
Общая ошибка: invalid literal for int() with base 10: 'н/д'

Traceback не появится — код рабочий, except ValueError просто никогда не получит своего исключения, потому что except Exception выше уже перехватит его первым.

ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«Первая подходящая ветка except перехватывает исключение, дальше Python не смотрит»Exception — родительский тип почти для всех ошибок. Поставленный первым, он перекрывает все более конкретные ветки ниже — их код просто никогда не выполняется, без единого предупреждения.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
Ошибка значения

Пояснение логики: та же логика, что и с elif в главе Условия — конкретные, узкие условия ставят выше, общие — ниже. except Exception уместен последней веткой-страховкой на случай непредвиденного, но не первой и не единственной.


11.3 else и finally, собственная проверка данных и raise

Зачем это нужно. else в связке с try выполняется, только если блок try отработал без единого исключения, — удобно отделить «код, который может упасть» от «код, который выполняется после успеха». finally выполняется всегда — упал try или нет, — для действий, которые обязаны произойти в любом случае. raise позволяет самому поднять исключение, если данные формально приводятся к нужному типу, но по смыслу неверны.

// PYTHON_EXECUTOR (Pyodide Wasm)
Обработана запись: '87'
87

Балл '150' пройдёт int() без проблем — это валидное число, но не валидный школьный балл. raise поднимает своё собственное исключение с понятным сообщением там, где стандартная ошибка Python не сработала бы сама.

⚠️ Подводный камень

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

// PYTHON_EXECUTOR (Pyodide Wasm)
[78, 92, 150]
Обработано баллов: 3

Скрипт не падает и выглядит рабочим, а результат тихо неверен сразу дважды: строка 'н/д' пропала без следа, а 150 — не отбракованный, а один из баллов, попавший в отчёт как есть.

ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«except ... pass перехватывает ошибку и ничего не делает»Исключение погашено, но факт потери строки нигде не зафиксирован. В итоговых цифрах («обработано 3 балла») не видно, что реальных записей было 4, а одна пропала молча.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
[78, 92]
Обработано баллов: 2, отбраковано: 2
Отбраковано 'н/д': invalid literal for int() with base 10: 'н/д'
Отбраковано '150': вне диапазона 0-100: 150

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


11.4 Мини-словарик ошибок одиннадцатой главы

ОшибкаЧто значит для PythonЧеловеческий перевод
ValueError (внутри except)«Значение не подходит по типу или по смыслу»Строку не удалось привести к числу (int('н/д')) либо число не прошло собственную проверку через raise.
ZeroDivisionError (внутри except)«Деление на ноль»Ученик без единой сдачи — пустой список баллов, calculate_average() из главы 9 делит сумму на len([]) == 0.
— (тихая ошибка)«Голый except: перехватывает любое исключение»Реальный баг в коде (например, опечатка в имени переменной) маскируется под «проблему с данными» и остаётся незамеченным.
— (тихая ошибка)«Первая подходящая ветка except забирает исключение себе»Широкая ветка (except Exception), поставленная выше конкретных, делает их недостижимыми — без единого предупреждения от Python.
— (тихая ошибка)«except ... pass перехватывает ошибку и ничего не делает»Строка молча выпадает из отчёта; итоговые цифры красивые, но не совпадают с реальным числом обработанных записей.

Мини-кейс: устойчивый парсер выгрузки — отчёт + журнал отбракованных строк

Задача: обработать выгрузку с неидеальными данными (пропущенные и текстовые значения, баллы вне диапазона), не потеряв ни одной строки без следа, — собрать отчёт по валидным записям и отдельный журнал отбракованных.

// PYTHON_EXECUTOR (Pyodide Wasm)
--- ОТЧЁТ ---
Иванов Иван: 78
Петров Пётр: 92
--- Обработано: 2, отбраковано: 3 ---
Отбраковано «Смирнова Ольга»: invalid literal for int() with base 10: 'н/д'
Отбраковано «Ким Юрий»: балл вне диапазона 0-100: 150
Отбраковано «Сидорова Анна»: invalid literal for int() with base 10: ''

Что получилось: три испорченные строки из пяти не обрушили скрипт и не растворились бесследно — каждая попала в rejected_rows с понятной причиной, а report.csv содержит только те записи, за которые можно ручаться. Цифры «обработано: 2, отбраковано: 3» в сумме честно дают все пять строк выгрузки — ни одна не потерялась незамеченной, в отличие от подводного камня из 11.3.

⚠️ Дальше в книге ошибки данных в отдельно взятой строке будут ловиться так же, но расчёты — медиана, распределение, работа с датами сдачи — потребуют инструментов из стандартной библиотеки Python, а не только собственноручно написанных функций. Это тема главы Стандартная библиотека.


Чек-лист главы

  • Оборачиваю в try / except код, который может упасть на кривых данных, а не весь скрипт целиком
  • Указываю конкретный тип исключения (except ValueError) вместо голого except:
  • Использую as error, чтобы достать текст ошибки, а не просто узнать, что она произошла
  • Ставлю конкретные ветки except выше общих (except Exception) — иначе общая ветка перекроет частные
  • Считаю и логирую отбракованные строки вместо except ... pass
  • Использую else для кода, который должен выполняться только при успешном try
  • Использую finally для действий, обязательных в любом случае — например, для лога «строка обработана»
  • Поднимаю собственное исключение через raise, если значение технически валидно, но не проходит по смыслу (балл вне 0-100)
  • Проверяю в конце, что сумма «обработано» и «отбраковано» совпадает с числом строк на входе

Дальше: Стандартная библиотека