如何创建回溯对象

Col*_*low 11 python traceback

我想创建一个类似于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代码.

所以无论你真正想做什么都是可行的,即使你所要求的不是.

  • Jinja2做了这个黑客加上一些更复杂的事情.https://github.com/mitsuhiko/jinja2/blob/9b4b20aa56fde3a5cd5ac49d4feacd96eacb832d/jinja2/debug.py (2认同)

Zby*_*byl 8

从 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)

相关文档在这里:


aba*_*ert 6

如果你真的需要欺骗另一个库 - 特别是用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_lastitb_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]