具有递归的 Python C API - 段错误
Posted
技术标签:
【中文标题】具有递归的 Python C API - 段错误【英文标题】:Python C API with recursion - segfaults 【发布时间】:2012-10-31 14:22:24 【问题描述】:我在 C++ 中使用 python 的 C API (2.7) 将 python 树结构转换为 C++ 树。代码如下:
python 树以递归方式实现为具有子列表的类。叶节点只是原始整数(不是类实例)
我加载一个模块并从 C++ 调用一个 python 方法,使用来自 here 的代码,它返回树的一个实例,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 代码)。它似乎发生在访问树的 last 叶节点时。
我很难确定问题所在。使用 C API 进行递归是否有任何特殊注意事项?我不确定我是否需要使用 Py_INCREF/Py_DECREF,或者使用 these 函数或其他东西。老实说,我并不完全理解 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");
【问题讨论】:
你检查了,所以你没有NULL
指针?
你能显示更多代码吗?也许您没有正确检查 NULL
s 或叶节点。 PyObject_GetAttr
返回一个新实例,因此您不必Py_INCREF
它,但请务必检查返回的值是否为NULL
(表示失败)。 PyList_GetItem
返回一个借来的引用,所以你应该不 Py_DECREF
它。如果您必须存储其结果以供将来使用,您必须使用 Py_INCREF
获取引用的所有权(但我认为您不必这样做)。
我在上面添加了一些代码。我已经进入调试模式并确保没有空指针。无法在 API 内调试。我被困住了。
【参考方案1】:
您可以识别 Python/C 代码的几个问题:
PyObject_IsInstance
将类而不是字符串作为其第二个参数。
没有专用于reference counting 的代码。新的引用,例如PyObject_GetAttr
返回的引用,永远不会被释放,而通过PyList_GetItem
获得的借用引用永远不会是acquired before use。将 C++ 异常与其他纯 Python/C 混合使用会加剧问题,使得实现正确的引用计数变得更加困难。
缺少重要的错误检查。 PyString_FromString
内存不足时会失败;如果列表同时缩小,PyList_GetItem
可能会失败;即使在PyObject_HasAttr
成功之后,PyObject_GetAttr
在某些情况下也会失败。
这是代码的重写(但未经测试)版本,具有以下更改:
实用函数GetExpressionTreeClass
从定义它的模块中获得ExpressionTree
类。 (为my_module
填写正确的模块名称。)
Guard
是一个RAII 风格的保护类,它在离开作用域时释放 Python 对象。这个小而简单的类使引用计数异常安全,它的构造函数自己处理 NULL 对象。 boost::python
定义了这种风格的功能层,我建议看看它。
所有Python_exception
throws 现在都附有设置 Python 异常信息。因此Python_exception
的捕手可以使用PyErr_PrintExc
或PyErr_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");
【讨论】:
谢谢,我非常感谢细节。我已经应用了你的更改,现在我在PyObject_IsInstance(child, cls)
遇到了段错误。有没有办法调试 python 的 C API 代码?来源是否可用?再次感谢。
我注意到,如果我在稍微不同的树上运行它,它甚至不会通过函数调用从 python 获取树(甚至没有到达我们的代码)在这里重新谈论)。 PyObject_Call
中发生段错误。让我想知道我的 python-dev 安装是否搞砸了。我似乎同时安装了 2.6 和 2.7。
Python/C API 的源代码是 Python 本身的源代码。如果你在 Unix 上运行,你可以解压它并用 ./configure
和 make
编译它。
也许您应该摆脱py_embed
库,直接使用Python/C API?这并不那么难。
是的,我只是想要一些入门代码。我发现this tutorial 更成功。以上是关于具有递归的 Python C API - 段错误的主要内容,如果未能解决你的问题,请参考以下文章
使用 python-c-api 调用的 Cython 回调段错误