Отладка A: что такое баг, виды ошибок и чтение Stack Trace
- Определить, что такое баг (Bug), и различать Syntax Error, Runtime Error и Logical Error
- Читать Stack Trace — определять тип ошибки, сообщение, файл и строку
- Знать распространённые ошибки в Python и причину каждой из них
- Понимать, почему Logical Error найти труднее всего
Что мы изучим?
- Что такое баг (Bug).
- Три вида ошибок: Syntax Error, Runtime Error, Logical Error.
- Как читать Stack Trace (Traceback) — сообщение об ошибке в Python.
- Самые распространённые ошибки и что говорит каждая из них.
Зачем это нужно?
Разработчики тратят огромную часть времени на поиск и исправление багов. Разница между новичком и профессионалом не в том, что у профессионала нет багов, а в том, что он читает сообщение об ошибке и за секунды понимает, куда смотреть. Новички видят много красного текста и паникуют. К концу урока красный текст станет картой.
Объяснение с основ
Что такое баг?
Баг (Bug) — разрыв между тем, что программа должна делать, и тем, что она делает на самом деле. Иногда программа падает, а иногда «работает», но возвращает неверный результат.
В 1947 году инженеры компьютера Mark II нашли мотылька (bug — «жук», «насекомое»), застрявшего в одном из реле, и вклеили его в рабочий журнал с подписью «first actual case of bug being found». Слово использовалось для технических неисправностей и раньше, но именно эта история сделала его знаменитым.
Три вида ошибок
| Вид | Когда обнаруживается | Сообщает ли о ней Python? | Пример |
|---|---|---|---|
| Syntax Error | До запуска (при проверке синтаксиса) | Да, и не выполняется ни одна строка | Пропущенный : или скобка |
| Runtime Error (исключение, Exception) | Во время выполнения, на проблемной строке | Да, а строки перед ней уже выполнились | Деление на 0, несуществующая переменная |
| Logical Error | Никогда автоматически | Нет! Код выполняется «успешно» | + вместо -, перевёрнутое условие |
Помните тему о компьютере, операционной системе и сети? Syntax Error обнаруживается, когда Python переводит код в байт-код (Bytecode), до выполнения. Runtime Error возникает, когда PVM выполняет определённую строку и терпит неудачу.
Logical Error — самая опасная: ни сообщения, ни красного цвета, просто неверный результат. Её находят, только если проверяют, что результат правильный.
Stack Trace — карта ошибки
Когда возникает Runtime Error, Python печатает Traceback (его также называют Stack Trace): путь, который прошёл код до ошибки.
Traceback (most recent call last):
File "shop.py", line 9, in <module>
total = checkout(cart)
File "shop.py", line 5, in checkout
return subtotal / count
ZeroDivisionError: division by zeroЧитайте снизу вверх:
- Последняя строка — тип ошибки (
ZeroDivisionError) и сообщение (division by zero). Всегда начинайте отсюда. - Строка над ней — где именно: файл
shop.py, строка 5, внутри функцииcheckout, и сам код. - Ещё выше — кто вызвал эту функцию: строка 9, на верхнем уровне файла (
<module>).
Почему «Stack»? Потому что каждый вызов функции кладётся в стек вызовов (Call Stack). Traceback показывает стопку в момент падения — от первого вызова (сверху) до последнего (снизу).
Распространённые ошибки в Python
| Ошибка | Значение | Частая причина |
|---|---|---|
SyntaxError | Код — недопустимый синтаксис | Пропущенный :, скобки, кавычки |
IndentationError | Неправильный отступ | Смешение пробелов, непоследовательный отступ |
NameError | Имя не определено | Опечатка, использование до определения |
TypeError | Операция над неподходящим типом | "5" + 3, вызов функции с неверным числом аргументов |
ValueError | Правильный тип, недопустимое значение | int("hello") |
ZeroDivisionError | Деление на ноль | Знаменатель, который не проверили |
IndexError | Индекс за пределами списка | items[5], когда элементов 3 |
KeyError | Ключ, которого нет в словаре | (изучим в теме структур данных и функций) |
AttributeError | У объекта нет такого атрибута/метода | "abc".push(), опечатка в имени метода |
FileNotFoundError | Файл не существует | Неверный путь, рабочая папка (Working Directory) |
Синтаксис
Нового синтаксиса нет. Инструмент — чтение. Шаблон любого Traceback:
Traceback (most recent call last):
File "<file>", line <N>, in <function or <module>>
<the code on that line>
... ← more frames, each a function call
<ErrorType>: <message> ← START READING HEREИ для Syntax Error (без Traceback, потому что ничего не выполнялось):
File "<file>", line <N>
<the code>
^ ← where Python noticed the problem
SyntaxError: <message>Первый пример кода
week-2/bug_report.py:
def average(numbers):
total = sum(numbers)
return total / len(numbers)
scores = []
print("Average score:", average(scores))Вывод:
Traceback (most recent call last):
File "C:\dev\kodkod6\week-2\bug_report.py", line 7, in <module>
print("Average score:", average(scores))
^^^^^^^^^^^^^^^
File "C:\dev\kodkod6\week-2\bug_report.py", line 3, in average
return total / len(numbers)
~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zeroРазбор кода построчно
Читаем Traceback снизу вверх:
| Строка вывода | Что она нам говорит |
|---|---|
ZeroDivisionError: division by zero | Что произошло: деление на ноль. |
line 3, in average + return total / len(numbers) | Где: строка 3, внутри average. ~~~^~~~ точно отмечает операцию деления. |
line 7, in <module> + average(scores) | Кто вызвал: строка 7, на верхнем уровне. <module> = «не внутри функции». |
Делаем вывод: len(numbers) равно 0, то есть список пуст. А кто передал пустой список? Строка 7 передала scores, который равен []. Настоящий баг в том, что мы не обработали случай пустого списка. Строка, которая упала (3), — это симптом, а решение, что делать с пустым списком, — исправление:
def average(numbers):
if len(numbers) == 0:
return 0
return sum(numbers) / len(numbers)Ещё один пример
Три вида ошибок в одной и той же функции. Функция должна вычислять цену после процентной скидки.
Syntax Error:
def price_after_discount(price, percent)
return price - price * percent / 100 File "discount.py", line 1
def price_after_discount(price, percent)
^
SyntaxError: expected ':'Не хватает двоеточия. Python не выполнил ни одной строки.
Runtime Error:
def price_after_discount(price, percent):
return price - price * percent / 100
print(price_after_discount("100", 10))Traceback (most recent call last):
File "discount.py", line 4, in <module>
print(price_after_discount("100", 10))
File "discount.py", line 2, in price_after_discount
return price - price * percent / 100
~~~~~~~~~~~~~~~~^~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'Синтаксис допустим, но во время выполнения price — текст. "100" * 10 «работает» (повторяет текст 10 раз!), но затем деление на 100 падает. ^ указывает на /.
Logical Error:
def price_after_discount(price, percent):
return price - percent / 100
print(price_after_discount(100, 10))99.9Никакой ошибки! Но скидка 10% на 100 должна вернуть 90.0. Мы забыли умножить на price. Python не может знать, что мы имели в виду. Только проверка по ожидаемому результату это выявит.
Типичная ошибка
Ошибка 1: читать Traceback сверху и паниковать. Всегда начинайте с последней строки: тип и сообщение. Затем строка над ней. Чаще всего этого достаточно.
Ошибка 2: исправлять строку, которая упала, а не причину. В первом примере «исправление» вроде return total / (len(numbers) + 1) убирает ошибку, но возвращает неверный результат во всех случаях! Это превратило Runtime Error в Logical Error. Ещё хуже.
Ошибка 3: искать Syntax Error только в строке, указанной в сообщении. Иногда проблема в предыдущей строке:
prices = [10, 20, 30
total = sum(prices) File "test.py", line 1
prices = [10, 20, 30
^
SyntaxError: '[' was never closedPython дошёл до строки 2 и обнаружил, что скобка из строки 1 так и не закрыта. В новых версиях сообщение точное, но в общем случае: если отмеченная строка выглядит нормально, проверьте строку перед ней.
Практика
Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.
Упражнение 1: Какой вид ошибки?
Для каждого фрагмента — это SyntaxError, Runtime Error или Logical Error? И в чём проблема?
# A
if age > 18
print("adult")
# B
print(total_price) # total_price was never defined
# C
def area(width, height):
return width + height # should be the area of a rectangleПоказать решение
- A — SyntaxError: в конце строки
ifне хватает двоеточия:. Не выполняется ни одна строка. - B — Runtime Error (
NameError): синтаксис допустим, но во время выполнения Python не может найти переменную с именемtotal_price. - C — Logical Error: ошибки нет вообще, но результат неверный — площадь вычисляется умножением,
width * height. Такую ошибку находят, только если проверяют результат.
Упражнение 2: Читаем Stack Trace
Traceback (most recent call last):
File "shop.py", line 12, in <module>
print(checkout([20, 30], 0))
File "shop.py", line 7, in checkout
return average_price(prices, count)
File "shop.py", line 3, in average_price
return sum(prices) / count
ZeroDivisionError: division by zero- Какой тип ошибки и какое сообщение?
- В каком файле, на какой строке и в какой функции она произошла?
- Откуда пришло проблемное значение?
Показать решение
ZeroDivisionError: division by zero— деление на ноль. Сначала читайте последнюю строку.- В файле
shop.py, строка 3, в функцииaverage_price— это самая нижняя строка в списке. - Читаем вверх:
average_priceвызвана из строки 7 вcheckout, аcheckoutвызвана из строки 12 сcount = 0. Значит, проблема началась в вызове на строке 12, который передал 0.
Упражнение 3: Логический баг в переводе температуры
def to_celsius(fahrenheit):
return fahrenheit - 32 * 5 / 9
print(to_celsius(212))
print(to_celsius(32))Результаты должны быть 100 и 0, но получается что-то другое. Найдите баг и исправьте его.
Ожидаемый вывод:
100.0
0.0Показать решение
def to_celsius(fahrenheit):
return (fahrenheit - 32) * 5 / 9
print(to_celsius(212))
print(to_celsius(32))100.0
0.0Порядок операций: умножение и деление выполняются раньше вычитания, поэтому исходный код вычислял fahrenheit - (32 * 5 / 9). Скобки заставляют вычитание выполниться первым. Это Logical Error — никакого сообщения об ошибке, просто неверный результат.
Упражнение 4: TypeError
age = 25
print("Age: " + age)- Какую ошибку вы получите и почему?
- Исправьте её двумя разными способами.
Ожидаемый вывод:
Age: 25Показать решение
TypeError: can only concatenate str (not "int") to str— нельзя соединять текст и число через+.- Два способа:
age = 25
print("Age: " + str(age))Age: 25age = 25
print("Age:", age)Age: 25Первый переводит число в текст. Второй передаёт print два значения, и он добавляет между ними пробел.
Упражнение 5: Среднее пустого списка
def average(numbers):
return sum(numbers) / len(numbers)
print(average([80, 90]))
print(average([]))- Что произойдёт при втором вызове и какую ошибку вы получите?
- Исправьте функцию так, чтобы для пустого списка она возвращала 0.
Ожидаемый вывод:
85.0
0Показать решение
len([])равно 0, поэтому вы получитеZeroDivisionError: division by zero— ошибку времени выполнения.- Проверьте этот случай до деления:
def average(numbers):
if len(numbers) == 0:
return 0
return sum(numbers) / len(numbers)
print(average([80, 90]))
print(average([]))85.0
0Итоги
- Баг = разрыв между желаемым и фактическим поведением.
- Syntax Error — до запуска, не выполняется ни одна строка. Runtime Error — посреди выполнения, с Traceback. Logical Error — без сообщения, просто неверный результат.
- Читайте Traceback снизу вверх: тип + сообщение → строка и функция → кто вызвал.
- Строка, которая упала, иногда лишь симптом. Ищите причину.
- Logical Error обнаруживается, только когда вы сравниваете результат с ожидаемым значением.