Доступ к трассировке 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?
Ответы
Ответ 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;
}