Трассировка и виды ошибок
Чему вы научитесь:
- читать трассировку снизу вверх и находить строку, где случилась ошибка;
- перехватывать исключения через
try/exceptи выбирать порядок веток; - выполнять код после успеха через
elseи в любом случае черезfinally; - возбуждать исключения через
raiseи объявлять свои классы исключений.

Представьте столбик из трёх ящиков, поставленных друг под другом. Верхний ящик — код программы вне функций. Он поручает работу ящику report, а тот — нижнему ящику average. Каждый ящик ждёт, пока нижний закончит и вернёт ответ.
Теперь нижний ящик трескается: average не может выполнить свою работу. Весть о поломке поднимается по пунктиру обратно вверх, мимо каждого ящика. Если её никто не остановит, остановится вся программа.
Так ошибка во время работы программы часто случается не в строке с вызовом, а глубоко внутри функций. Функция average делит сумму чисел на их количество, и для пустого списка это деление на ноль:
def average(nums):
return sum(nums) / len(nums)
def report(nums):
print("average:", average(nums))
report([4, 6]) # average: 5.0
report([])Traceback (most recent call last):
File "main.py", line 10, in <module>
report([])
File "main.py", line 6, in report
print("average:", average(nums))
^^^^^^^^^^^^^
File "main.py", line 2, in average
return sum(nums) / len(nums)
~~~~~~~~~~^~~~~~~~~~~
ZeroDivisionError: division by zeroДавайте разберём, что произошло:
- Вызов
report([4, 6])работает:averageвозвращает5.0, и программа печатаетaverage: 5.0. - Вызов
report([])передаёт пустой список вreport, а та — вaverage. - В
averageвыражениеlen(nums)равно0, и деление на ноль ломает работу. - Ни
average, ниreportошибку не обрабатывают, поэтому программа останавливается и печатает отчёт.
Чтение снизу вверх
Когда случается ошибка, Python создаёт исключение — объект, который описывает, что пошло не так. Если программа его не перехватывает, она останавливается и печатает трассировку. Трассировка — это отчёт о пути исключения: какие вызовы оно прошло и где возникло.
Трассировка перечисляет вызовы, через которые прошла программа. Что в ней есть:
- на каждый вызов — строка с файлом, номером строки и именем функции;
- под ней — сама строка кода;
- имя
<module>обозначает код вне функций, наш верхний ящик; - значки
^и~отмечают часть строки, которая выполнялась в момент ошибки; - слова
most recent call lastзначат, что последний вызов стоит внизу.
Читайте трассировку снизу вверх, начиная с последней строки:
- Последняя строка называет вид исключения и причину:
ZeroDivisionError, деление на ноль. - Над ней — место ошибки: строка 2 в функции
average, треснувший ящик. - Ещё выше — кто её вызвал: строка 6 в
report. - В самом верху — строка 10 в
<module>, откуда всё началось.
Исправлять часто нужно не последнюю функцию, а вызов, который передал ей неподходящие данные, — здесь пустой список.
Здесь аналогия с ящиками немного расходится с экраном. Исключение поднимается снизу вверх, а трассировка печатает ящики сверху вниз, в порядке вызовов. Поэтому глаза идут от последней строки к первой.
Виды исключений
Вид исключения подсказывает, что искать, а текст после двоеточия уточняет причину. У KeyError это сам ключ, которого нет, у ValueError — значение, которое не подошло. Эти пять видов встречаются чаще других:
| Вид | Пример | Когда возникает |
|---|---|---|
ValueError | int("ten") | тип подходит, а значение — нет |
TypeError | "a" + 1 | операция не подходит к типу |
KeyError | {"a": 1}["b"] | в словаре нет такого ключа |
IndexError | [1, 2][5] | индекс за концом списка или строки |
ZeroDivisionError | 1 / 0 | деление на ноль, в том числе // и % |
Семейство исключений
Каждый вид исключения — это класс, а само исключение — экземпляр этого класса. Классы исключений связаны наследованием: ValueError — дочерний класс Exception, а Exception — дочерний класс BaseException. Потомки класса — его дочерние классы, их дочерние классы и так далее.
Все пять классов из таблицы — потомки Exception, а все исключения — потомки BaseException. Экземпляр потомка считается и экземпляром каждого класса выше него. Экземпляр исключения можно создать и без ошибки, чтобы проверить это через isinstance:
error = ValueError("bad")
print(isinstance(error, ValueError)) # True
print(isinstance(error, Exception)) # True
print(isinstance(error, BaseException)) # True
print(isinstance(KeyboardInterrupt(), Exception)) # FalseИсключение KeyboardInterrupt возникает, когда программу останавливают сочетанием клавиш Ctrl+C. Оно наследует BaseException напрямую, минуя Exception: это не ошибка в программе, а команда остановиться. Все ошибки самой программы — потомки Exception.
На собеседовании часто спрашивают, чем Exception отличается от BaseException. Ответ:
BaseException— корень всех исключений;Exception— его потомок и родитель всех ошибок программы;- команды остановки вроде
KeyboardInterruptнаследуютBaseExceptionнапрямую, минуяException.
Главное
- Исключение — объект, который описывает ошибку; непойманное исключение останавливает программу и печатает трассировку.
- Трассировку читают снизу вверх: последняя строка — вид и причина, над ней — место ошибки, выше — кто вызывал.
- Каждый вид исключения — класс; все ошибки программы — потомки
Exception, а все исключения — потомкиBaseException. KeyboardInterruptнаследуетBaseExceptionнапрямую, поэтому он не потомокException.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.