Как создать объект traceback

Я хочу создать трассировку, такую как возвращаемая sys.exc_info()[2]. Мне не нужен список строк, мне нужен реальный объект traceback:

<traceback object at 0x7f6575c37e48>

Как я могу это сделать? Моя цель состоит в том, чтобы включить текущий стек минус один кадр, поэтому он выглядит вызывающим последним вызовом.

3 ответов


нет документированного способа создания объектов трассировки.

ни одна из функций traceback модуль создавать их. Вы, конечно, можете открыть тип types.TracebackType, но если вы вызываете его конструктор, вы просто получаете TypeError: cannot create 'traceback' instances.

причина этого в том, что tracebacks содержат ссылки на внутренние компоненты, которые вы не можете получить доступ или создать из Python.


тем не менее, вы можете получить доступ к кадрам стека, и все остальное, что вам нужно для имитации трассировки, тривиально. Вы даже можете написать класс, который имеет tb_frame, tb_lasti, tb_lineno и tb_next атрибуты (используя информацию, которую вы можете получить от traceback.extract_stack и один inspect functions), который будет выглядеть точно так же, как трассировка любого кода pure-Python.

так что есть хороший шанс, что вы действительно хотите сделать это выполнимо, хотя то, что вы просите, это не.


Если вам действительно нужно обмануть другую библиотеку, особенно написанную на C и использующую непубличный API, есть два потенциальных способа получить реальный объект трассировки. Ни один из них не работает надежно. Кроме того, оба они специфичны для CPython, требуют не только использования уровня API C, но и использования недокументированных типов и функций, которые могут измениться в любой момент, и предлагают потенциал для новых и захватывающих возможностей сегментировать ваш интерпретатор. Но если вы хотите попробовать, они могут быть полезны для начала.


на PyTraceBack type не является частью открытого API. Но (за исключением того, что он определен в каталоге Python вместо каталога объектов) он построен как тип API C, просто не документирован. Итак, если вы посмотрите на traceback.h и traceback.c для версии Python, вы увидите, что... ну, нет PyTraceBack_New, но нет is a PyTraceBack_Here это создает новую трассировку и меняет ее на текущую информацию об исключении. Я не уверен, что это допустимо, чтобы вызвать это, если нет текущего исключения, и если есть is текущее исключение вы можете испортить его, изменив его так, но с небольшим количеством проб и сбоев или чтения кода, надеюсь, вы можете заставить это работать:

import ctypes
import sys

ctypes.pythonapi.PyTraceBack_Here.argtypes = (ctypes.py_object,)
ctypes.pythonapi.PyTraceBack_Here.restype = ctypes.c_int

def _fake_tb():
    try:
        1/0
    except:
        frame = sys._getframe(2)
        if ctypes.pythonapi.PyTraceBack_Here(frame):
            raise RuntimeError('Oops, probably hosed the interpreter')
        raise

def get_tb():
    try:
        _fake_tb()
    except ZeroDivisionError as e:
       return e.__traceback__

в качестве забавной альтернативы мы можем попытаться мутировать объект traceback на лету. Чтобы получить объект traceback, просто поднимите и поймайте исключение:

try: 1/0
except exception as e: tb = e.__traceback__ # or sys.exc_info()[2]

единственная проблема в том, что это указывая на свой кадр стека, а не вашего абонента, верно? Если бы tracebacks были изменяемыми, вы могли бы легко исправить это:

tb.tb_lasti, tb.tb_lineno = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno
tb.tb_frame = tb.tb_frame.f_back

и нет никаких методов для установки этих вещей, либо. Обратите внимание, что у него нет setattro, и getattro работы по строительству __dict__ на лету, так что, очевидно, единственный способ добраться до этого материала-через базовую структуру. Который вы действительно должны построить с ctypes.Structure, но как быстрый Хак:

p8 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_ulong))
p4 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_uint))

теперь, для обычная 64-битная сборка CPython,p8[:2] / p4[:4] нормальные заголовке объекта, а после этого пришла отладочных полей, так p8[3] - это tb_frame и p4[8] и p4[9] это tb_lasti и tb_lineno, соответственно. Итак:

p4[8], p4[9] = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno

но следующая часть немного сложнее, потому что tb_frame на самом деле не PyObject *, это просто сырой struct _frame *, так что иди в frameobject.h, где вы видите, что это действительно PyFrameObject * так что вы можете просто использовать повторить трюк. Просто не забудьте _ctypes.Py_INCREF следующий кадр кадра и Py_DECREF сам кадр после выполнения переназначения p8[3] на pf8[3], или как только вы попытаетесь напечатать трассировку, вы будете segfault и потеряете всю работу, которую вы сделали, написав это. :)


как указывали другие, невозможно создать объекты трассировки. Однако, вы можете написать свой собственный класс, который имеет те же свойства:

from collections import namedtuple
fake_tb = namedtuple('fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next'))

вы все еще можете передавать экземпляры этого класса некоторым функциям Python. Особенно,traceback.print_exception(...), который производит тот же вывод, что и стандартный excepthook Python.

Если вы (как я) столкнулись с этой проблемой, потому что вы работаете на PyQt на основе GUI приложение, вы также можете быть заинтересованы в более комплексное решение выложено в этот блог.