Python C API with segfaults



我在C++中使用python的C API(2.7)将python树结构转换为C++树。代码如下:

  • python树被递归地实现为一个具有子级列表的类。叶节点只是基元整数(而不是类实例)

  • 我从C++加载一个模块并调用一个python方法,使用这里的代码,它将树的一个实例python_tree作为C++中的PyObject返回。

  • 递归遍历获得的PyObject。为了获得孩子的名单,我这样做:

    PyObject* attr = PyString_FromString("children");
    PyObject* list = PyObject_GetAttr(python_tree,attr);
    for (int i=0; i<PyList_Size(list); i++) {
    PyObject* child = PyList_GetItem(list,i); 
    ...
    

非常简单,它有效,直到我最终在调用PyObject_GetAttr时遇到分段错误(Objects/object.c:1193,但我看不到API代码)。这似乎发生在访问树的最后一个叶节点时。

我很难确定这个问题。在使用C API进行递归时,是否有任何特殊考虑?我不确定是否需要使用Py_INCREF/Py_DECREF,或者使用这些函数或其他什么。老实说,我不完全理解API是如何工作的。非常感谢您的帮助!

编辑:一些最小代码:

void VisitTree(PyObject* py_tree) throw (Python_exception)
{
PyObject* attr = PyString_FromString("children");
if (PyObject_HasAttr(py_tree, attr)) // segfault on last visit
{
PyObject* list = PyObject_GetAttr(py_tree,attr);
if (list)
{
int size = PyList_Size(list);
for (int i=0; i<size; i++)
{
PyObject* py_child = PyList_GetItem(list,i);
PyObject *cls = PyString_FromString("ExpressionTree");
// check if child is class instance or number (terminal)
if (PyInt_Check(py_child) || PyLong_Check(py_child) || PyString_Check(py_child)) 
;// terminal - do nothing for now
else if (PyObject_IsInstance(py_child, cls))
VisitTree(py_child);
else
throw Python_exception("unrecognized object from python");
}
}
}
}

可以识别Python/C代码的几个问题:

  • PyObject_IsInstance将类而非字符串作为其第二个参数。

  • 没有专门用于引用计数的代码。新的引用,例如PyObject_GetAttr返回的引用,在使用之前永远不会被释放,使用PyList_GetItem获得的借用引用也不会被获取。将C++异常与纯Python/C混合会加剧这个问题,使实现正确的引用计数变得更加困难。

  • 缺少重要的错误检查。当内存不足时,PyString_FromString可能会失败;如果同时列表收缩,则PyList_GetItem可能失败;即使在PyObject_HasAttr成功之后,PyObject_GetAttr在某些情况下也可能失败。

这是一个重写(但未经测试)的代码版本,具有以下更改:

  • 实用函数GetExpressionTreeClass从定义它的模块中获得ExpressionTree类。(为my_module填写正确的模块名称。)

  • Guard是一个RAII风格的保护类,它在离开作用域时释放Python对象。这个小而简单的类使得引用计数异常是安全的,并且它的构造函数本身处理NULL对象。boost::python定义了这种风格的功能层,我建议看一下

  • 现在,所有Python_exception抛出都伴随着设置Python异常信息。因此,Python_exception的捕获器可以使用PyErr_PrintExcPyErr_Fetch来打印异常或以其他方式找出问题所在。

代码:

class Guard {
PyObject *obj;
public:
Guard(PyObject *obj_): obj(obj_) {
if (!obj)
throw Python_exception("NULL object");
}
~Guard() {
Py_DECREF(obj);
}
};
PyObject *GetExpressionTreeClass()
{
PyObject *module = PyImport_ImportModule("my_module");
Guard module_guard(module);
return PyObject_GetAttrString(module, "ExpressionTree");
}
void VisitTree(PyObject* py_tree) throw (Python_exception)
{
PyObject *cls = GetExpressionTreeClass();
Guard cls_guard(cls);
PyObject* list = PyObject_GetAttrString(py_tree, "children");
if (!list && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();  // hasattr does this exact check
return;
}
Guard list_guard(list);
Py_ssize_t size = PyList_Size(list);
for (Py_ssize_t i = 0; i < size; i++) {
PyObject* child = PyList_GetItem(list, i);
Py_XINCREF(child);
Guard child_guard(child);
// check if child is class instance or number (terminal)
if (PyInt_Check(child) || PyLong_Check(child) || PyString_Check(child)) 
; // terminal - do nothing for now
else if (PyObject_IsInstance(child, cls))
VisitTree(child);
else {
PyErr_Format(PyExc_TypeError, "unrecognized %s object", Py_TYPE(child)->tp_name);
throw Python_exception("unrecognized object from python");
}
}
}

相关内容

  • 没有找到相关文章

最新更新