1

我在 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 代码)。它似乎发生在访问树的最后一个叶子节点时。

我很难确定问题所在。使用 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");
            }
        }
    }
}
4

1 回答 1

6

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

  • PyObject_IsInstance接受一个类,而不是一个字符串,作为它的第二个参数。

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

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

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

  • 实用程序函数从定义它的模块中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");
    }
  }
}
于 2012-11-12T20:30:46.907 回答