通过C API访问Python traceback

33 投票
9 回答
14895 浏览
提问于 2025-04-15 16:22

我在用C语言的接口处理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的源代码时,我发现可以通过一个叫做_frame的结构来访问当前帧的文件名和模块名。这个结构看起来是私有定义的。接下来我想尝试用程序的方式加载Python的'traceback'模块,并通过C接口调用它的函数。这种做法合理吗?有没有更好的方法从C语言访问Python的错误追踪信息?

9 个回答

14

我更喜欢从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);
    }
}
19

这个问题虽然比较老,但为了将来参考,你可以从线程状态对象中获取当前的调用栈,然后向后遍历这些调用栈。除非你想保留当前的状态,否则不需要使用 traceback 对象。

举个例子:

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;
    }
}
9

我发现_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);

不过我觉得这还是挺不干净的。

撰写回答