我想创建一个类似于sys.exc_info()[2]返回的回溯。我不想要一个行列表,我想要一个实际的回溯对象:
<traceback object at 0x7f6575c37e48>
我该怎么做?我的目标是让它包含当前堆栈减去一帧,因此看起来呼叫者是最近的呼叫。
答案 0 :(得分:12)
没有记录的方法来创建回溯对象。
traceback
模块中的所有功能都不会创建它们。您当然可以将类型设置为types.TracebackType
,但如果您调用其构造函数,则只需获得TypeError: cannot create 'traceback' instances
。
原因是回溯包含对您无法在Python中实际访问或生成的内部的引用。
但是,您可以访问堆栈帧,而模拟回溯所需的其他所有内容都是微不足道的。您甚至可以编写一个包含tb_frame
,tb_lasti
,tb_lineno
和tb_next
属性的类(使用您可以从traceback.extract_stack
获取的信息和其中一个inspect
函数),它看起来就像是对任何纯Python代码的回溯。
答案 1 :(得分:5)
如果你真的需要欺骗另一个库 - 特别是用C语言编写并使用非公共API - 有两种可能的方法来获得真正的回溯对象。我没有让任何一个人可靠地工作。此外,两者都是特定于CPython的,不仅要求使用C API层,而且要求使用随时可能发生变化的未记录的类型和功能,并提供新的和令人兴奋的机会来解析您的解释器。但是如果你想尝试,它们可能对一开始就很有用。
PyTraceBack
类型不是公共API的一部分。但是(除了在Python目录而不是Object目录中定义)它是作为C API类型构建的,只是没有记录。因此,如果您查看Python版本的traceback.h
和traceback.c
,您会看到......嗯,没有PyTraceBack_New
,但是 a { {1}}构造一个新的回溯并将其交换为当前的异常信息。我不确定调用它是有效的,除非有当前异常,并且如果 当前异常,你可能会通过像这样改变它来搞砸它,但是有点试验和崩溃或者阅读代码,希望你可以使用它:
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
现在,对于CPython的正常64位版本,p8 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_ulong))
p4 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_uint))
/ 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 *
,所以关闭转到frameobject.h
,你看到它真的是struct _frame *
所以你可以再次使用相同的技巧。在重新分配PyFrameObject *
以指向_ctypes.Py_INCREF
之后,或者在您尝试打印回溯时,请记住Py_DECREF
框架的下一帧和p8[3]
框架本身ll段错误并失去你写完这篇文章的所有工作。 :)
答案 2 :(得分:1)
从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)
相关文档在这里:
答案 3 :(得分:0)
正如其他人所指出的那样,创建回溯对象是不可能的。但是,您可以编写自己的具有相同属性的类:
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中列出的更全面的解决方案感兴趣。
答案 4 :(得分:0)
“为了更好地支持堆栈跟踪的动态创建,现在可以从Python代码实例化type.TracebackType,并且现在可以写回溯的tb_next属性。”
(在python 3.7中)对此有一个解释(在python 3.7中)https://docs.python.org/3/library/types.html#types.TracebackType