Python 如何创建回溯对象

Python 如何创建回溯对象,python,traceback,Python,Traceback,我想创建一个类似sys.exc_info()[2]返回的回溯。我不想要行列表,我想要一个实际的回溯对象: <traceback object at 0x7f6575c37e48> 我该怎么做?我的目标是让它包含当前堆栈减去一帧,这样看起来调用方是最近的调用。没有文档记录的方法来创建回溯对象 模块中的任何函数都不会创建它们。当然,您可以作为访问该类型,但是如果调用其构造函数,您只会得到一个TypeError:cannotcreate'traceback'实例 这是因为回溯包含对内

我想创建一个类似sys.exc_info()[2]返回的回溯。我不想要行列表,我想要一个实际的回溯对象:

<traceback object at 0x7f6575c37e48>


我该怎么做?我的目标是让它包含当前堆栈减去一帧,这样看起来调用方是最近的调用。

没有文档记录的方法来创建回溯对象

模块中的任何函数都不会创建它们。当然,您可以作为访问该类型,但是如果调用其构造函数,您只会得到一个
TypeError:cannotcreate'traceback'实例

这是因为回溯包含对内部构件的引用,而您实际上无法从Python中访问或生成这些内部构件


然而,您可以访问堆栈帧,而模拟回溯所需的其他一切都是微不足道的。您甚至可以编写一个具有
tb\u frame
tb\u lasti
tb\u lineno
tb\u next
属性的类(使用您可以从中获得的信息和其中一个函数),这些属性看起来完全像是对任何纯Python代码的回溯


如果你真的需要愚弄另一个库,特别是一个用C编写并使用非公共API的库,有两种可能的方法可以获得真正的回溯对象。我两个都不能可靠地工作。此外,它们都是特定于CPython的,不仅需要使用C API层,还需要使用可能随时更改的未记录类型和函数,并为您的解释器提供了新的和令人兴奋的机会。但是,如果你想尝试,它们可能是一个有用的开始


PyTraceBack
类型不是公共API的一部分。但是(除了在Python目录中而不是在Object目录中定义之外),它是作为一种C-API类型构建的,只是没有文档记录。因此,如果您查看Python版本,您将看到……没有
PyTraceBack\u New
,但是这里有一个
PyTraceBack\u
,它构造了一个新的回溯并将其交换到当前异常信息中。我不确定调用此函数是否有效,除非存在当前异常,如果存在当前异常,您可能会通过这样的变异将其搞糟,但通过一点尝试和崩溃或阅读代码,希望您能让它正常工作:

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
通过动态构建
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
来工作,因此显然我们获取这些内容的唯一方法是通过底层结构。您应该真正使用
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\u帧
p4[8]
p4[9]
分别是
tb\u-lasti
tb\u行号。因此:

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

但是下一部分比较难,因为
tb\u-frame
实际上不是一个
PyObject*
,它只是一个原始的
struct\u-frame*
,所以您可以继续,在那里您看到它实际上是一个
PyFrameObject*
,所以您可以再次使用相同的技巧。只要记住在重新分配
p8[3]
指向
pf8[3]
后,增加帧的下一帧,并减少帧本身,或者在尝试打印回溯时,您就会中断并丢失编写此回溯的所有工作。:)

正如其他人指出的,不可能创建回溯对象。但是,您可以编写具有相同属性的自己的类:

from collections import namedtuple
fake_tb = namedtuple('fake_tb', ('tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next'))
您仍然可以将此类的实例传递给某些Python函数。最值得注意的是,它产生与Python的标准excepthook相同的输出

如果您(和我一样)因为使用基于PyQt的GUI应用程序而遇到此问题,您可能还对中列出的更全面的解决方案感兴趣。

“为了更好地支持堆栈跟踪的动态创建,现在可以从Python代码实例化types.TracebackType,并且traceback上的tb_next属性现在是可写的。”


这里(Python3.7)有一个相同的解释(在Python3.7中)

因为Python3.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)
相关文件如下:


你想要这个回溯做什么?回溯的结果。
traceback.extract\u stack
和/或
inspect.stack
不是一个
traceback
对象,但它也不仅仅是一个行列表。其中一个就足够了吗?第三方库只接受回溯对象,我不想对它进行黑客攻击。是吗?”仅接受回溯对象“平均”检查
isinstance(t,types.TracebackType)
?如果是这样……那么,如果
traceback
是少数几种不允许自己用作基础的类型之一,我也不会感到惊讶,但我至少要先检查一下。如果不是,这意味着什么?Jinja2就是这个黑客加上一些更复杂的东西@digenishjkl那里有些不错的东西。特别是,在添加额外功能的同时动态代理真实的回溯对于许多用例来说是一个明显的好主意;我应该想到这一点……在pytest中构建调试器时,它也很有用,因此您可以过滤掉
\uuu tracebackhide\uu
帧。