Глава 11. Ошибки и исключения: `try` / `except`
Задача главы: не давать одной кривой строке в выгрузке обрушить весь отчёт — перехватывать ошибки в нужном месте, считать отбракованные строки и продолжать обработку остальных.
11.1 try / except: перехват ValueError на приведении типов
Зачем это нужно. Глава Файлы и CSV закончилась предупреждением: одна испорченная строка в выгрузке — например, пустое значение вместо балла — валит int() исключением и обрывает весь скрипт, даже если остальные тысяча строк в полном порядке. try / except перехватывает ошибку в конкретном месте и позволяет решить, что делать дальше, вместо того чтобы просто падать.
Не удалось привести к числу: 'н/д'Python выполняет блок try; если внутри возникает исключение указанного типа (ValueError), выполнение сразу передаётся в except, а не прерывает скрипт. Если исключения не произошло, блок except просто не выполняется.
⚠️ Подводный камень
Голый except: без указания типа перехватывает вообще любое исключение — включая опечатки и баги в самом коде, а не только ожидаемые проблемы с данными.
Что-то пошло не так с данными0| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «Голый except: перехватывает любое исключение» | Настоящая причина падения — NameError из-за опечатки scors — исчезла за общим сообщением «что-то пошло не так с данными». Баг в коде выглядит как проблема с данными и может остаться незамеченным надолго. |
Правильный паттерн
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 позволяют обработать каждый случай по-своему и посчитать, сколько строк в итоге пришлось отбраковать.
Иванов Иван: 71.7Смирнова Ольга: пропущен (division by zero)Отбраковано строк: 1except ZeroDivisionError as error кладёт объект исключения в error — из него можно вытащить текст сообщения, как в примере выше, а не просто узнать, что что-то произошло.
⚠️ Подводный камень
Порядок веток важен: except Exception — самый широкий тип, перехватывающий почти всё. Если поставить его первым, более конкретные ветки ниже никогда не выполнятся.
Общая ошибка: invalid literal for int() with base 10: 'н/д'Traceback не появится — код рабочий, except ValueError просто никогда не получит своего исключения, потому что except Exception выше уже перехватит его первым.
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «Первая подходящая ветка except перехватывает исключение, дальше Python не смотрит» | Exception — родительский тип почти для всех ошибок. Поставленный первым, он перекрывает все более конкретные ветки ниже — их код просто никогда не выполняется, без единого предупреждения. |
Правильный паттерн
Ошибка значенияПояснение логики: та же логика, что и с elif в главе Условия — конкретные, узкие условия ставят выше, общие — ниже. except Exception уместен последней веткой-страховкой на случай непредвиденного, но не первой и не единственной.
11.3 else и finally, собственная проверка данных и raise
Зачем это нужно. else в связке с try выполняется, только если блок try отработал без единого исключения, — удобно отделить «код, который может упасть» от «код, который выполняется после успеха». finally выполняется всегда — упал try или нет, — для действий, которые обязаны произойти в любом случае. raise позволяет самому поднять исключение, если данные формально приводятся к нужному типу, но по смыслу неверны.
Обработана запись: '87'87Балл '150' пройдёт int() без проблем — это валидное число, но не валидный школьный балл. raise поднимает своё собственное исключение с понятным сообщением там, где стандартная ошибка Python не сработала бы сама.
⚠️ Подводный камень
Перехватить исключение и ничего с этим не сделать — самый тихий способ терять данные: строка молча пропадает из отчёта, и ничто не намекает, что она вообще существовала.
[78, 92, 150]Обработано баллов: 3Скрипт не падает и выглядит рабочим, а результат тихо неверен сразу дважды: строка 'н/д' пропала без следа, а 150 — не отбракованный, а один из баллов, попавший в отчёт как есть.
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «except ... pass перехватывает ошибку и ничего не делает» | Исключение погашено, но факт потери строки нигде не зафиксирован. В итоговых цифрах («обработано 3 балла») не видно, что реальных записей было 4, а одна пропала молча. |
Правильный паттерн
[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 перехватывает ошибку и ничего не делает» | Строка молча выпадает из отчёта; итоговые цифры красивые, но не совпадают с реальным числом обработанных записей. |
Мини-кейс: устойчивый парсер выгрузки — отчёт + журнал отбракованных строк
Задача: обработать выгрузку с неидеальными данными (пропущенные и текстовые значения, баллы вне диапазона), не потеряв ни одной строки без следа, — собрать отчёт по валидным записям и отдельный журнал отбракованных.
--- ОТЧЁТ ---Иванов Иван: 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) - Проверяю в конце, что сумма «обработано» и «отбраковано» совпадает с числом строк на входе
Дальше: Стандартная библиотека