Traceback в Python: как читать и находить свою ошибку

Traceback в Python: как читать и находить свою ошибку Полезное

Traceback — это отчет интерпретатора Python о цепочке вызовов функций, которая привела к необработанному исключению. Он печатается в консоль, когда программа падает, и содержит путь по стеку вызовов плюс тип и текст ошибки в последней строке. В статье разберем, как читать traceback, где искать свою строку кода и чем он отличается от exception и error.

Traceback, exception и error — в чем разница

Эти три слова часто путают, хотя это разные вещи.

Exception (исключение) — это объект в памяти, который Python создает при сбое: ValueError, KeyError, TypeError и другие. Его можно поймать блоком try/except, у него есть тип и сообщение.

Error (ошибка) — в Python это, как правило, синоним подкласса Exception, который обозначает именно программную ошибку (ValueError, TypeError), а не любое исключение вообще (есть исключения, которые ошибкой не являются, например StopIteration). В разговорной речи error часто используют шире — как «что-то пошло не так».

Traceback (трассировка) — это не сам сбой, а отчет о пути к нему: список кадров стека от точки входа в программу до строки, где исключение возникло.

Цепочка происходящего: raise ValueError("...") создает объект-исключение -> он поднимается вверх по стеку вызовов -> если ни один except его не поймал, интерпретатор печатает traceback и в последней строке — тип и текст исключения.

Из чего состоит traceback

Возьмем типичный вывод:

Traceback (most recent call last):
  File "shop.py", line 13, in <module>
    print(compute_total(items))
  File "shop.py", line 8, in compute_total
    total += parse_price(item)
  File "shop.py", line 2, in parse_price
    return float(text)
ValueError: could not convert string to float: 'abc'

Здесь три части:

  • Последняя строка — тип исключения и его сообщение (ValueError: could not convert...). Это самое важное: что именно сломалось.
  • Блоки кадров (File ... line ... in ...) — каждый кадр это файл, номер строки и имя функции, где выполнение было в момент сбоя. Под каждым кадром — сама строка кода.
  • Заголовок Traceback (most recent call last) — предупреждение, что порядок кадров идет от самого раннего вызова к самому позднему (то есть к месту сбоя).

Как читать traceback: алгоритм

  1. Начните с последней строки — тип и текст исключения. Это диагноз.
  2. Поднимитесь на один кадр вверх (блок прямо над последней строкой) — это место, где исключение фактически возникло.
  3. Проверьте путь файла в этом кадре. Если это site-packages, lib/python3.x или другой не ваш путь — это библиотечный код, и настоящая причина обычно в вызове чуть выше.
  4. Продолжайте подниматься по кадрам, пока не найдете первый файл из вашего проекта — это и есть строка, которую нужно чинить.
  5. Если кадров несколько в вашем же коде, кадр ближе к концу списка обычно точнее указывает на симптом, а кадр выше — на причину (откуда пришли неверные данные).

Разберем на примере выше. Последняя строка говорит: ValueError: could not convert string to float: 'abc'. Кадр над ней — parse_price, строка 2, return float(text): здесь float() не смог разобрать строку. Кадр выше — compute_total, строка 8: здесь parse_price вызвали с плохим значением. Кадр <module>, строка 13 — точка входа. Если весь код ваш, чинить нужно либо валидацию в parse_price, либо фильтрацию входных данных в compute_total.

Полный пример с воспроизводимой ошибкой

Код в файле shop.py:

def parse_price(text):
    return float(text)


def compute_total(items):
    total = 0
    for item in items:
        total += parse_price(item)
    return total


items = ["10.5", "20", "abc"]
print(compute_total(items))

Запуск python shop.py даст ровно тот traceback, что разобран выше:

Traceback (most recent call last):
  File "shop.py", line 13, in <module>
    print(compute_total(items))
  File "shop.py", line 8, in compute_total
    total += parse_price(item)
  File "shop.py", line 2, in parse_price
    return float(text)
ValueError: could not convert string to float: 'abc'

Исправление — проверять значение до преобразования и пропускать некорректные строки:

def parse_price(text):
    try:
        return float(text)
    except ValueError:
        return None


def compute_total(items):
    total = 0
    for item in items:
        price = parse_price(item)
        if price is not None:
            total += price
    return total


items = ["10.5", "20", "abc"]
print(compute_total(items))

Вывод после исправления:

30.5

Значение 'abc' теперь просто пропускается, а не роняет программу.

Модуль traceback: print_exc и format_exc

Если исключение поймано в try/except, Python не печатает traceback сам — его нужно вывести вручную. Для этого есть встроенный модуль traceback.

traceback.print_exc() печатает трассировку текущего перехваченного исключения в поток ошибок (stderr), не прерывая программу:

import traceback

try:
    compute_total(["10.5", "abc"])
except ValueError:
    traceback.print_exc()
    print("Программа продолжает работать")

Вывод:

Traceback (most recent call last):
  File "shop.py", line 20, in <module>
    compute_total(["10.5", "abc"])
  File "shop.py", line 8, in compute_total
    total += parse_price(item)
  File "shop.py", line 2, in parse_price
    return float(text)
ValueError: could not convert string to float: 'abc'
Программа продолжает работать

traceback.format_exc() делает то же самое, но возвращает текст строкой, а не печатает — это удобно для логирования:

import logging
import traceback

try:
    compute_total(["10.5", "abc"])
except ValueError:
    logging.error("Не удалось посчитать сумму:\n%s", traceback.format_exc())

Так полный traceback попадет в файл лога вместе с остальными сообщениями, а не потеряется в консоли.

Когда traceback длиннее: цепочки исключений

Иногда обработка одного исключения вызывает второе. Тогда Python печатает обе трассировки подряд, разделяя их строкой During handling of the above exception, another exception occurred:. Первая напечатанная трассировка — исходная причина, вторая — то, что случилось при попытке ее обработать. Читать нужно обе, начиная с верхней (исходной) части.

Выводы

  • Traceback — это отчет о пути вызовов до сбоя, а не сам сбой; exception — объект ошибки, error — обычно подкласс Exception для программных ошибок.
  • Начинайте чтение с последней строки (тип и текст ошибки), затем поднимайтесь по кадрам вверх.
  • Первый кадр с путем внутри вашего проекта — это строка, которую нужно чинить; библиотечные кадры обычно указывают на симптом, а не причину.
  • traceback.print_exc() и traceback.format_exc() нужны, когда исключение уже поймано в except, а трассировку все равно нужно увидеть или записать в лог.
  • Строка During handling of the above exception... означает, что перед вами две связанные трассировки, а не одна.

Где применяется / связь с практикой

Освойте тему на практике

Чтение traceback — базовый навык отладки, без которого сложно разбираться даже в чужом коде: логах веб-сервера, тестах, продакшен-ошибках. Если вы только начинаете работать с Python и хотите закрыть такие пробелы системно, а не по отдельным статьям, посмотрите курс Python с нуля — там разбор ошибок и отладка идут отдельным модулем на практике. Проверить формат подачи можно и без обязательств — на открытых уроках Otus.

FAQ

Почему в traceback фигурирует stdin вместо имени файла?
Так бывает при работе в интерактивном режиме REPL: код выполняется не из файла, а построчно из ввода, поэтому вместо пути указывается stdin, а строки кода под кадром не показываются.

Можно ли получить traceback в виде переменной, а не печатать его?
Да, для этого traceback.format_exc() возвращает готовый текст строкой, а traceback.extract_tb() — структурированный список кадров, если нужно обрабатывать их программно.

Чем StopIteration отличается от типичных error-исключений вроде ValueError?
StopIteration — служебный сигнал для итераторов (что элементы закончились), а не признак программной ошибки; в большинстве кода его ловит сам механизм for, и разработчик с ним напрямую не работает.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)