Skip to main content
 首页 » 编程设计

具有递归的 Python C API之段错误

2025年05月04日19mfrbuaa

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

  • python 树以递归方式实现为具有子级列表的类。叶节点只是原始整数(不是类实例)

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

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

    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 或使用 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"); 
            } 
        } 
    } 
} 

请您参考如下方法:

可以识别您的 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 -style 保护类,在离开作用域时释放 Python 对象。这个小而简单的类使引用计数异常安全,并且它的构造函数自己处理 NULL 对象。 boost::python以这种方式定义功能层,我建议看一看。

  • 所有 Python_exception 抛出现在都伴随着设置 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"); 
    } 
  } 
}