我想创建一个类似于sys.exc_info()[2]返回的回溯.我不想要一个行列表,我想要一个实际的回溯对象:
<traceback object at 0x7f6575c37e48>
Run Code Online (Sandbox Code Playgroud)
我怎样才能做到这一点?我的目标是让它包含当前堆栈减去一帧,因此看起来呼叫者是最近的呼叫.
aba*_*ert 12
没有记录的方法来创建回溯对象.
traceback模块中的所有功能都不会创建它们.你当然可以访问类型types.TracebackType,但如果你调用它的构造函数,你就得到一个TypeError: cannot create 'traceback' instances.
这样做的原因是回溯包含对您无法在Python中实际访问或生成的内部的引用.
但是,您可以访问堆栈帧,而模拟回溯所需的其他所有内容都是微不足道的.你甚至可以编写具有级tb_frame,tb_lasti,tb_lineno,和tb_next属性(使用信息你可以从traceback.extract_stack和的一个inspect功能),这将看起来就像一个回溯到任何纯Python代码.
从 Python 3.7 开始,您可以从 Python 动态创建回溯对象。
要创建与 raise 创建的回溯相同的回溯:
raise Exception()
Run Code Online (Sandbox Code Playgroud)
用这个:
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)
Run Code Online (Sandbox Code Playgroud)
相关文档在这里:
如果你真的需要欺骗另一个库 - 特别是用C语言编写并使用非公共API - 有两种可能的方法来获得真正的回溯对象.我没有让任何一个人可靠地工作.此外,两者都是特定于CPython的,不仅要求使用C API层,而且要求使用随时可能发生变化的未记录的类型和功能,并提供新的和令人兴奋的机会来解析您的解释器.但是如果你想尝试,它们可能对一开始有用.
该PyTraceBack类型不是公共API的一部分.但是(除了在Python目录中定义而不是在Object目录中定义)它是作为C API类型构建的,只是没有记录.所以,如果你看一下traceback.h,并traceback.c为您的Python版本,你会看到...好,没有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__
Run Code Online (Sandbox Code Playgroud)
作为一种有趣的替代方案,我们可以尝试动态变异追溯对象.要获取回溯对象,只需引发并捕获异常:
try: 1/0
except exception as e: tb = e.__traceback__ # or sys.exc_info()[2]
Run Code Online (Sandbox Code Playgroud)
唯一的问题是它指向你的堆栈帧,而不是你的来电者,对吗?如果回溯是可变的,您可以轻松解决:
tb.tb_lasti, tb.tb_lineno = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno
tb.tb_frame = tb.tb_frame.f_back
Run Code Online (Sandbox Code Playgroud)
并且没有办法设置这些东西.请注意,它没有a 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))
Run Code Online (Sandbox Code Playgroud)
现在,对于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
Run Code Online (Sandbox Code Playgroud)
但下一部分有点难,因为tb_frame实际上不是一个PyObject *,它只是一个原始的struct _frame *,所以关闭你去frameobject.h,你看到它真的是一个PyFrameObject *所以你可以再次使用相同的技巧.在重新分配指向后,记住_ctypes.Py_INCREF框架的下一帧和Py_DECREF框架本身,或者一旦你尝试打印回溯,你就会发生段错并丢失你写完这些文件的所有工作.:)p8[3]pf8[3]