Skip to content Skip to sidebar Skip to footer

Accessing A Python Traceback From The C Api

I'm having some trouble figuring out the proper way to walk a Python traceback using the C API. I'm writing an application that embeds the Python interpreter. I want to be able t

Solution 1:

This is an old question but for future reference, you can get the current stack frame from the thread state object and then just walk the frames backward. A traceback object isn't necessary unless you want to preserve the state for the future.

For example:

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

Solution 2:

I prefer calling into python from C:

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

Solution 3:

I've discovered that _frame is actually defined in the frameobject.h header included with Python. Armed with this plus looking at traceback.c in the Python C implementation, we have:

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

PyTracebackObject* traceback = get_the_traceback();

int line = traceback->tb_lineno;
constchar* filename = PyString_AsString(traceback->tb_frame->f_code->co_filename);

But this still seems really dirty to me.

Solution 4:

One principal I've found useful in writing C extensions is to use each language where it's best suited. So if you have a task to do that would be best implemented in Python, implement in Python, and if it would be best implemented in C, do it in C. Interpreting tracebacks is best done in Python for two reasons: first, because Python has the tools to do it, and second, because it isn't speed-critical.

I would write a Python function to extract the info you need from the traceback, then call it from C.

You could even go so far as to write a Python wrapper for your callable execution. Instead of invoking someCallablePythonObject, pass it as an argument to your Python function:

definvokeSomeCallablePythonObject(obj, args):
    try:
        result = obj(*args)
        ok = Trueexcept:
        # Do some mumbo-jumbo with the traceback, etc.
        result = myTraceBackMunger(...)
        ok = Falsereturn ok, result

Then in your C code, call this Python function to do the work. The key here is to decide pragmatically which side of the C-Python split to put your code.

Solution 5:

I used the following code to extract Python exception's error body. strExcType stores the exception type and strExcValue stores the exception body. Sample values are:

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

Cpp code:

if(PyErr_Occurred() != NULL) {
    PyObject *pyExcType;
    PyObject *pyExcValue;
    PyObject *pyExcTraceback;
    PyErr_Fetch(&pyExcType, &pyExcValue, &pyExcTraceback);
    PyErr_NormalizeException(&pyExcType, &pyExcValue, &pyExcTraceback);

    PyObject* str_exc_type = PyObject_Repr(pyExcType);
    PyObject* pyStr = PyUnicode_AsEncodedString(str_exc_type, "utf-8", "Error ~");
    constchar *strExcType =  PyBytes_AS_STRING(pyStr);

    PyObject* str_exc_value = PyObject_Repr(pyExcValue);
    PyObject* pyExcValueStr = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~");
    constchar *strExcValue =  PyBytes_AS_STRING(pyExcValueStr);

    // When using PyErr_Restore() there is no need to use Py_XDECREF for these 3 pointers//PyErr_Restore(pyExcType, pyExcValue, pyExcTraceback);Py_XDECREF(pyExcType);
    Py_XDECREF(pyExcValue);
    Py_XDECREF(pyExcTraceback);

    Py_XDECREF(str_exc_type);
    Py_XDECREF(pyStr);

    Py_XDECREF(str_exc_value);
    Py_XDECREF(pyExcValueStr);
}

Post a Comment for "Accessing A Python Traceback From The C Api"