Как создать объект 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 приложение, вы также можете быть заинтересованы в более комплексное решение выложено в этот блог.