我想创建一个类似于sys.exc_info()[2]返回的回溯。我不想要一个行列表,我想要一个实际的回溯对象:
<traceback object at 0x7f6575c37e48>
我怎样才能做到这一点?我的目标是让它包含当前堆栈减去一帧,因此看起来呼叫者是最近的呼叫。
没有记录的方法来创建回溯对象。
traceback
模块中没有任何函数可以创建它们。您当然可以访问类型为types.TracebackType
,但如果您调用它的构造函数,您只需获得一个TypeError: cannot create 'traceback' instances
。
这样做的原因是回溯包含对您无法在Python中实际访问或生成的内部的引用。
但是,您可以访问堆栈帧,而模拟回溯所需的其他所有内容都是微不足道的。你甚至可以编写一个包含tb_frame
,tb_lasti
,tb_lineno
和tb_next
属性的类(使用traceback.extract_stack
和inspect
函数之一获得的信息),它看起来就像是对任何纯Python代码的追溯。
如果你真的需要欺骗另一个库 - 特别是用C语言编写并使用非公共API - 有两种可能的方法来获得真正的回溯对象。我没有让任何一个人可靠地工作。此外,两者都是特定于CPython的,不仅要求使用C API层,而且要求使用随时可能发生变化的未记录的类型和功能,并提供新的和令人兴奋的机会来解析您的解释器。但是如果你想尝试,它们可能对一开始有用。
PyTraceBack
类型不是公共API的一部分。但是(除了在Python目录而不是Object目录中定义)它是作为C API类型构建的,只是没有记录。所以,如果你看看你的Python版本的traceback.h
和traceback.c
,你会看到......好吧,没有PyTraceBack_New
,但是有一个PyTraceBack_Here
构造一个新的回溯并将其交换到当前的异常信息。我不确定调用它是有效的,除非有当前异常,并且如果有当前异常你可能会通过这样改变它来搞砸它,但是通过一些试验和崩溃或者阅读代码,希望你能得到这个工作:
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__
作为一种有趣的替代方案,我们可以尝试动态变异追溯对象。要获取回溯对象,只需引发并捕获异常:
try: 1/0
except exception as e: tb = e.__traceback__ # or sys.exc_info()[2]
唯一的问题是它指向你的堆栈帧,而不是你的来电者,对吗?如果回溯是可变的,您可以轻松解决:
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))
现在,对于CPython的正常64位构建,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]
之后,或者一旦你试图打印回溯你就会陷入错误并失去你写完这一切的所有工作。 :)
从Python 3.7开始,您可以从Python动态创建回溯对象。 要创建与raise创建的跟踪相同的跟踪:
raise Exception()
用这个:
import sys
import types
def exception_with_traceback(message):
tb = None
depth = 0
while True:
try:
frame = sys._getframe(depth)
depth += 1
except ValueError as exc:
break
tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
return Exception(message).with_traceback(tb)
相关文档在这里:
正如其他人指出的那样,不可能创建回溯对象。但是,您可以编写自己的具有相同属性的类:
from collections import namedtuple
fake_tb = namedtuple('fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next'))
您仍然可以将此类的实例传递给某些Python函数。最值得注意的是,traceback.print_exception(...)
产生与Python标准不同的输出。
如果您(像我一样)因为您正在使用基于PyQt的GUI应用程序而遇到此问题,您可能还会对this blog post中提供的更全面的解决方案感兴趣。
“为了更好地支持堆栈跟踪的动态创建,types.TracebackType现在可以从Python代码实例化,并且tracebacks上的tb_next属性现在是可写的。”
这里有一个解释(在python 3.7中)(python 3.7)https://docs.python.org/3/library/types.html#types.TracebackType