从C API访问Python回溯

从C API访问Python回溯,python,Python,我很难找到使用C API进行Python回溯的正确方法。我正在编写一个嵌入Python解释器的应用程序。我希望能够执行任意的Python代码,如果它引发异常,将其转换成我自己的应用程序特定的C++异常。现在,只提取引发Python异常的文件名和行号就足够了。这就是我到目前为止所做的: PyObject* pyresult = PyObject_CallObject(someCallablePythonObject, someArgs); if (!pyresult) { PyObject

我很难找到使用C API进行Python回溯的正确方法。我正在编写一个嵌入Python解释器的应用程序。我希望能够执行任意的Python代码,如果它引发异常,将其转换成我自己的应用程序特定的C++异常。现在,只提取引发Python异常的文件名和行号就足够了。这就是我到目前为止所做的:

PyObject* pyresult = PyObject_CallObject(someCallablePythonObject, someArgs);
if (!pyresult)
{
    PyObject* excType, *excValue, *excTraceback;
    PyErr_Fetch(&excType, &excValue, &excTraceback);
    PyErr_NormalizeException(&excType, &excValue, &excTraceback);

    PyTracebackObject* traceback = (PyTracebackObject*)traceback;
    // Advance to the last frame (python puts the most-recent call at the end)
    while (traceback->tb_next != NULL)
        traceback = traceback->tb_next;

    // At this point I have access to the line number via traceback->tb_lineno,
    // but where do I get the file name from?

    // ...       
}

翻阅Python源代码,我看到他们通过
\u frame
结构访问当前框架的文件名和模块名,这看起来像是一个私有定义的结构。我的下一个想法是以编程方式加载Python“回溯”模块,并使用C API调用其函数。这是理智的吗?有没有更好的方法从C访问Python回溯?

我发现在编写C扩展时有用的一个原则是在最适合的地方使用每种语言。因此,如果要执行的任务最好用Python实现,请用Python实现,如果最好用C实现,请用C实现。解释回溯最好用Python完成,原因有两个:第一,因为Python有工具来完成,第二,因为它不是速度关键型的

我将编写一个Python函数,从回溯中提取所需的信息,然后从C调用它

甚至可以为可调用执行编写Python包装器。不要调用
someCallablePythonObject
,而是将其作为参数传递给Python函数:

def invokeSomeCallablePythonObject(obj, args):
    try:
        result = obj(*args)
        ok = True
    except:
        # Do some mumbo-jumbo with the traceback, etc.
        result = myTraceBackMunger(...)
        ok = False
    return ok, result

然后在C代码中,调用这个Python函数来完成这项工作。这里的关键是实用地决定将代码放在C-Python拆分的哪一边。

我发现
\u frame
实际上是在Python附带的
frameobject.h
头中定义的。通过查看Python c实现中的
traceback.c
,我们可以:

#include <Python.h>
#include <frameobject.h>

PyTracebackObject* traceback = get_the_traceback();

int line = traceback->tb_lineno;
const char* filename = PyString_AsString(traceback->tb_frame->f_code->co_filename);
#包括
#包括
PyTracebackObject*traceback=get_the_traceback();
int line=回溯->tb\U lineno;
const char*filename=PyString\u AsString(回溯->tb\u帧->f\u代码->co\u文件名);

但这对我来说仍然是一个很糟糕的问题。

这是一个老问题,但为了将来的参考,您可以从thread state对象获取当前堆栈帧,然后向后遍历这些帧。除非您希望为将来保留状态,否则不需要回溯对象

例如:

PyThreadState *tstate = PyThreadState_GET();
if (NULL != tstate && NULL != tstate->frame) {
    PyFrameObject *frame = tstate->frame;

    printf("Python stack trace:\n");
    while (NULL != frame) {
        // int line = frame->f_lineno;
        /*
         frame->f_lineno will not always return the correct line number
         you need to call PyCode_Addr2Line().
        */
        int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
        const char *filename = PyString_AsString(frame->f_code->co_filename);
        const char *funcname = PyString_AsString(frame->f_code->co_name);
        printf("    %s(%d): %s\n", filename, line, funcname);
        frame = frame->f_back;
    }
}

我更喜欢从C调用python:

err = PyErr_Occurred();
if (err != NULL) {
    PyObject *ptype, *pvalue, *ptraceback;
    PyObject *pystr, *module_name, *pyth_module, *pyth_func;
    char *str;

    PyErr_Fetch(&ptype, &pvalue, &ptraceback);
    pystr = PyObject_Str(pvalue);
    str = PyString_AsString(pystr);
    error_description = strdup(str);

    /* See if we can get a full traceback */
    module_name = PyString_FromString("traceback");
    pyth_module = PyImport_Import(module_name);
    Py_DECREF(module_name);

    if (pyth_module == NULL) {
        full_backtrace = NULL;
        return;
    }

    pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
    if (pyth_func && PyCallable_Check(pyth_func)) {
        PyObject *pyth_val;

        pyth_val = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL);

        pystr = PyObject_Str(pyth_val);
        str = PyString_AsString(pystr);
        full_backtrace = strdup(str);
        Py_DECREF(pyth_val);
    }
}

我最近在为numpy编写分配跟踪程序时有理由这么做。前面的答案很接近,但是
frame->f\u lineno
并不总是返回正确的行号——您需要调用
PyFrame\u GetLineNumber()
。以下是更新的代码片段:

#include "frameobject.h"
...

PyFrameObject* frame = PyEval_GetFrame();
int lineno = PyFrame_GetLineNumber(frame);
PyObject *filename = frame->f_code->co_filename;
PyFrameObject中还提供了完整线程状态;如果要遍历堆栈,请继续在
f_back
上迭代,直到它为NULL。签出frameobject中的完整数据结构。h:


另请参见:

我使用以下代码提取Python异常的错误体
strExcType
存储异常类型,
strExcValue
存储异常主体。样本值为:

strExcType:"<class 'ImportError'>"
strExcValue:"ImportError("No module named 'nonexistingmodule'",)"

您可以访问类似于
tb\u printinternal
函数的Python回溯。它在
PyTracebackObject
list上迭代。我也尝试了上面的建议来迭代帧,但它对我不起作用(我只看到最后一个堆栈帧)

CPython代码摘录:

static int
tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name)
{
    int err;
    PyObject *line;

    if (filename == NULL || name == NULL)
        return -1;
    line = PyUnicode_FromFormat("  File \"%U\", line %d, in %U\n",
                                filename, lineno, name);
    if (line == NULL)
        return -1;
    err = PyFile_WriteObject(line, f, Py_PRINT_RAW);
    Py_DECREF(line);
    if (err != 0)
        return err;
    /* ignore errors since we can't report them, can we? */
    if (_Py_DisplaySourceLine(f, filename, lineno, 4))
        PyErr_Clear();
    return err;
}

static int
tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit)
{
    int err = 0;
    long depth = 0;
    PyTracebackObject *tb1 = tb;
    while (tb1 != NULL) {
        depth++;
        tb1 = tb1->tb_next;
    }
    while (tb != NULL && err == 0) {
        if (depth <= limit) {
            err = tb_displayline(f,
                                 tb->tb_frame->f_code->co_filename,
                                 tb->tb_lineno,
                                 tb->tb_frame->f_code->co_name);
        }
        depth--;
        tb = tb->tb_next;
        if (err == 0)
            err = PyErr_CheckSignals();
    }
    return err;
}
static int
tb_显示行(PyObject*f,PyObject*文件名,int-lineno,PyObject*名称)
{
INTERR;
PyObject*行;
如果(文件名==NULL | |名称==NULL)
返回-1;
line=PyUnicode\U FromFormat(“文件\%U\”,第%d行,在%U\n中”,
文件名、行号、名称);
如果(行==NULL)
返回-1;
err=PyFile\u WriteObject(行,f,Py\u PRINT\u RAW);
Py_DECREF(行);
如果(错误!=0)
返回错误;
/*忽略错误,因为我们不能报告错误,可以吗*/
if(_Py_DisplaySourceLine(f,文件名,行号,4))
PyErr_Clear();
返回错误;
}
静态整数
tb_printinternal(PyTracebackObject*tb,PyObject*f,长限)
{
int err=0;
长深度=0;
PyTracebackObject*tb1=tb;
while(tb1!=NULL){
深度++;
tb1=tb1->tb\U下一步;
}
while(tb!=NULL&&err==0){
如果(深度tb\U帧->f\U代码->co\U文件名,
tb->tb\U lineno,
tb->tb\U框架->f\U代码->co\U名称);
}
深度--;
tb=tb->tb\U下一步;
如果(错误==0)
err=PyErr_CheckSignals();
}
返回错误;
}

我不知道这有什么帮助。我不是在写扩展模块,而是在嵌入解释器。为了实现您的解决方案(如果我理解您的话),我将不得不编写一个Python代码的BUB,并将其存储在我的C++代码中作为字符串。然后在某个时刻,我必须编译代码,用它创建一个函数,然后通过PyObject\u CallObject调用该函数。与仅仅检查C语言中的本机堆栈框架结构相比,这似乎是一项繁重的工作。根据语言的强项在语言之间进行实用划分的建议非常有意义,但是我对尝试执行任意Python来处理执行其他任意Python时的错误状态感到谨慎<每次调用
PyObject_Str
后,都需要对code>pystr进行解密,并且
pyth_模块
也需要进行解密。为了澄清,此代码是Ned Batchholder回答中提出的myTraceBackMunger()的实现。关键是,即使发生了异常,Python解释器仍然可以使用,并且应该使用,因为它对回溯的处理处于更高的级别,您不需要这样做
static int
tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name)
{
    int err;
    PyObject *line;

    if (filename == NULL || name == NULL)
        return -1;
    line = PyUnicode_FromFormat("  File \"%U\", line %d, in %U\n",
                                filename, lineno, name);
    if (line == NULL)
        return -1;
    err = PyFile_WriteObject(line, f, Py_PRINT_RAW);
    Py_DECREF(line);
    if (err != 0)
        return err;
    /* ignore errors since we can't report them, can we? */
    if (_Py_DisplaySourceLine(f, filename, lineno, 4))
        PyErr_Clear();
    return err;
}

static int
tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit)
{
    int err = 0;
    long depth = 0;
    PyTracebackObject *tb1 = tb;
    while (tb1 != NULL) {
        depth++;
        tb1 = tb1->tb_next;
    }
    while (tb != NULL && err == 0) {
        if (depth <= limit) {
            err = tb_displayline(f,
                                 tb->tb_frame->f_code->co_filename,
                                 tb->tb_lineno,
                                 tb->tb_frame->f_code->co_name);
        }
        depth--;
        tb = tb->tb_next;
        if (err == 0)
            err = PyErr_CheckSignals();
    }
    return err;
}