Traceback в Python: извлечение и форматирование стека вызовов
Понимание и использование traceback для отладки
Модуль traceback предоставляет инструменты для работы с трассировкой стека вызовов при возникновении исключения. Основной способ получить полный отчет об ошибке - перехватить исключение и вызвать traceback.print_exc(). Этот метод печатает трассировку в стандартный поток ошибок (sys.stderr).
import traceback
try:
result = 1 / 0
except ZeroDivisionError:
traceback.print_exc()Client error python (ошибка http-клиента в python)
Вывод будет содержать стек вызовов, файл, номер строки и само исключение. Для получения строки без вывода на консоль используют traceback.format_exc(). Это удобно для записи в лог или отправки по сети.
import traceback
try:
with open('missing.txt') as f:
data = f.read()
except FileNotFoundError:
err_str = traceback.format_exc()
# err_str содержит полную трассировку как строку
print(err_str[:100]) # первые 100 символовNo installed python found (python не найден в системе)
Типичная проблема: если исключение не перехвачено, traceback создается автоматически и программа завершается. Для перехвата и контроля следует оборачивать опасный код в try/except.
Как получить трассировку в виде структурированных данных?
Функция traceback.extract_tb() извлекает из объекта трассировки (tb) список записей FrameSummary. Каждая запись содержит имя файла, номер строки, имя функции и исходный код строки (если доступен).
import sys, traceback
try:
x = [1, 2][5]
except IndexError:
tb = sys.exc_info()[2]
summary = traceback.extract_tb(tb)
for frame in summary:
print(f'{frame.filename}:{frame.lineno} in {frame.name}')
print(f' code: {frame.line}')
Python traceback using (трассировка ошибок в python)
Если sys.exc_info() возвращает None (вне обработчика исключения), traceback.extract_tb() не сработает. Используйте traceback.format_stack() для получения текущего стека.
Как записать трассировку в лог через logging?
Модуль logging имеет метод exception(), который автоматически добавляет трассировку текущего исключения. Он удобен для централизованного логирования.
import logging
logging.basicConfig(level=logging.ERROR)
try:
d = {'key': 'value'}
print(d['missing'])
except KeyError:
logging.exception('Ошибка доступа к ключу словаря')Python pip not found (ошибка 'pip not found' в python)
Если logging.exception() вызывается вне блока except, он не содержит информации об исключении. Следует вызывать его только внутри обработчика.
Как кастомизировать формат вывода traceback?
Модуль позволяет контролировать отображение с помощью format_list() и format_stack(). Можно убрать лишние фреймы, изменить порядок или добавить свои аннотации.
import traceback, sys
def custom_traceback():
stack = traceback.format_stack()[-3:-1] # последние два фрейма
print(''.join(stack))
def deep():
custom_traceback()
def outer():
deep()
outer()Unable to locate package python (ошибка 'unable to locate package' в python)
При обрезании стека легко потерять важную информацию. Рекомендуется сохранять полную трассировку для отладки, а для вывода оставлять только релевантные фреймы.
Как обрабатывать цепочки исключений (raise from)?
Использование raise ... from ... создает цепочку исключений. Модуль traceback через TracebackException может рекурсивно обрабатывать всю цепочку.
import traceback
try:
try:
1/0
except ZeroDivisionError as e:
raise RuntimeError('Ошибка вычисления') from e
except RuntimeError:
tb_exc = traceback.TracebackException(*sys.exc_info())
print(''.join(tb_exc.format()))
При глубоких цепочках вывод становится объемным. Стоит ограничить глубину через traceback.print_exception(chain=False) или параметр limit.
Ниже представлены расширенные примеры использования traceback в нестандартных сценариях.
Пример 1. Фильтрация фреймов по имени модуля
import traceback, sys
def filter_frames(tb, exclude_prefixes=['site-packages']):
"""Убираем из трассировки фреймы, путь к которым начинается с одного из префиксов."""
from traceback import FrameSummary
frames = traceback.extract_tb(tb)
filtered = []
for frame in frames:
if not any(frame.filename.startswith(prefix) for prefix in exclude_prefixes):
filtered.append(frame)
return traceback.format_list(filtered)
try:
import nonexistent_module
except ImportError:
tb = sys.exc_info()[2]
clean_lines = filter_frames(tb)
print(''.join(clean_lines))
File "<stdin>", line 11, in filter_frames
frames = traceback.extract_tb(tb)
File "<stdin>", line 1, in <module>
import nonexistent_module
ImportError: No module named 'nonexistent_module'
В этом примере фильтр исключает все фреймы из стандартных библиотек, оставляя только код пользователя.
Пример 2. Получение трассировки из другого потока
import threading, traceback, time
def worker():
try:
raise ValueError('Ошибка в потоке')
except:
# Сохраняем информацию для передачи
err_info = traceback.format_exc()
print(f'Ошибка в рабочем потоке:\n{err_info}')
t = threading.Thread(target=worker)
t.start()
t.join()
Ошибка в рабочем потоке:
Traceback (most recent call last):
File "<stdin>", line 4, in worker
raise ValueError('Ошибка в потоке')
ValueError: Ошибка в потоке
Пример 3. Кастомный обработчик с повторным выбросом
import traceback, sys
class CustomError(Exception):
pass
def risky():
try:
risky2()
except ValueError:
# Логируем исходную трассировку и выбрасываем новое исключение
with open('error.log', 'a') as f:
traceback.print_exc(file=f)
raise CustomError('Не удалось выполнить операцию') from None
def risky2():
raise ValueError('Некорректное значение')
try:
risky()
except CustomError:
traceback.print_exc()
Traceback (most recent call last):
File "<stdin>", line 17, in <module>
risky()
File "<stdin>", line 9, in risky
raise CustomError('Не удалось выполнить операцию') from None
CustomError: Не удалось выполнить операцию
Здесь исходный ValueError записан в файл, а пользователю показано только новое исключение с чистым стеком.