Подтвердить что ты не робот

Доступ к трассировке Python из API C

У меня возникли проблемы с выяснением надлежащего способа отслеживания трассировки Python с использованием C API. Я пишу приложение, в которое встроен интерпретатор Python. Я хочу, чтобы иметь возможность выполнять произвольный код Python, и если он вызывает исключение, перевести его на мое собственное исключение С++ для конкретного приложения. На данный момент достаточно извлечь только имя файла и номер строки, где было вызвано исключение 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, которая выглядит как частная структура. Моя следующая идея заключалась в программной загрузке модуля "traceback" Python и вызове его функций с помощью API C. Это здорово? Есть ли лучший способ получить доступ к трассировке Python из C?

4b9b3361

Ответ 1

Я обнаружил, что _frame фактически определен в заголовке frameobject.h, включенном в Python. Вооруженный этим плюсом, смотрящим на traceback.c в реализации Python 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);

Но это все еще кажется мне грязным.

Ответ 2

Это старый вопрос, но для справки в будущем вы можете получить текущий стек стека из объекта состояния потока, а затем просто пройти кадры назад. Объект трассировки не нужен, если вы не хотите сохранить состояние в будущем.

Например:

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

Ответ 3

Я предпочитаю вызывать python из 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);
    }
}

Ответ 4

Один из основных, который я нашел полезным в написании расширений 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 разбита на ваш код.

Ответ 5

У меня была причина сделать это недавно, написав трекер выделения для numpy. Предыдущие ответы близки, но frame->f_lineno не всегда вернет правильный номер строки - вам нужно позвонить PyFrame_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: http://svn.python.org/projects/python/trunk/Include/frameobject.h

Смотрите также: https://docs.python.org/2/c-api/reflection.html

Ответ 6

Я использовал следующий код, чтобы извлечь исключение Python тело ошибки. strExcType хранит тип исключения, а strExcValue хранит тело исключения. Примеры значений:

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

Код Cpp:

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 ~");
    const char *strExcType =  PyBytes_AS_STRING(pyStr);

    PyObject* str_exc_value = PyObject_Repr(pyExcValue);
    PyObject* pyExcValueStr = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~");
    const char *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);
}

Ответ 7

Вы можете получить доступ к трассировке Python, аналогичной функции tb_printinternal. Он перебирает список PyTracebackObject. Я попробовал также предложения выше, чтобы перебирать кадры, но это не работает для меня (я вижу только последний стек кадров).

Выдержки из кода 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;
}