我在使用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源代码中,我看到他们通过_frame
结构访问当前帧的文件名和模块名,看起来它是一个私有定义的结构。我的下一个想法是以编程方式加载Python'traceback'模块并使用C API调用其函数。这样理智吗?有没有更好的方法从C访问Python回溯?
答案 0 :(得分:11)
这是一个老问题但是为了将来参考,您可以从线程状态对象获取当前堆栈帧,然后向后遍历帧。除非您希望保留将来的状态,否则不需要回溯对象。
例如:
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;
}
}
答案 1 :(得分:11)
我更喜欢从C:
调用pythonerr = 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);
}
}
答案 2 :(得分:8)
我发现_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);
但这对我来说似乎仍然很脏。
答案 3 :(得分:3)
我发现在编写C扩展时有用的一个原则是使用最适合的每种语言。因此,如果你有一个任务要做,最好用Python实现,用Python实现,如果最好用C实现,用C语言做。解释回溯最好用Python完成,原因有两个:第一,因为Python有工具可以做到这一点,其次,因为它不是速度关键的。
我会编写一个Python函数来从traceback中提取所需的信息,然后从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拆分的哪一侧放置你的代码。
答案 4 :(得分:2)
我最近有理由在为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
,直到它为空。检查frameobject.h中的完整数据结构:http://svn.python.org/projects/python/trunk/Include/frameobject.h
答案 5 :(得分:1)
我使用以下代码提取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);
}
答案 6 :(得分:0)
您可以访问类似于cancelPreviousPerformRequests
函数的Python跟踪。它迭代tb_printinternal
列表。我已经尝试了上面的建议来迭代帧,但它对我不起作用(我只看到最后一个堆栈帧)。
摘自CPython代码:
PyTracebackObject