11

我正在编写一个不使用 Cython 的 Python C 扩展。

我想在 C 中分配一个双精度数组,在内部函数中使用它(恰好在 Fortran 中)并返回它。我指出 C-Fortran 接口在 C 中完美运行。

static PyObject *
Py_drecur(PyObject *self, PyObject *args)
{
  // INPUT
  int n;
  int ipoly;
  double al;
  double be;

  if (!PyArg_ParseTuple(args, "iidd", &n, &ipoly, &al, &be))
    return NULL;

  // OUTPUT
  int nd = 1;
  npy_intp dims[] = {n};
  double a[n];
  double b[n];
  int ierr;

  drecur_(n, ipoly, al, be, a, b, ierr);

  // Create PyArray
  PyObject* alpha = PyArray_SimpleNewFromData(nd, dims, NPY_DOUBLE, a);
  PyObject* beta = PyArray_SimpleNewFromData(nd, dims, NPY_DOUBLE, b);

  Py_INCREF(alpha);
  Py_INCREF(beta);

  return Py_BuildValue("OO", alpha, beta);
}

我调试了这段代码,当我尝试从 alpha 中创建 alpha 时出现分段错误。到那里一切正常。函数 drecur_ 有效,如果将其删除,我会遇到同样的问题。

现在,围绕 C 数据定义 PyArray 的标准方法是什么?我找到了文档,但没有很好的例子。另外,内存泄漏怎么办?在返回之前对 INCREF 是否正确,以便保留 alpha 和 beta 的实例?当不再需要它们时,释放怎么办?

编辑 我终于用NumPy cookbook中的方法做对了。

static PyObject *
Py_drecur(PyObject *self, PyObject *args)
{
  // INPUT
  int n;
  int ipoly;
  double al;
  double be;
  double *a, *b;
  PyArrayObject *alpha, *beta;

  if (!PyArg_ParseTuple(args, "iidd", &n, &ipoly, &al, &be))
    return NULL;

  // OUTPUT
  int nd = 1;
  int dims[2];
  dims[0] = n;
  alpha = (PyArrayObject*) PyArray_FromDims(nd, dims, NPY_DOUBLE);
  beta = (PyArrayObject*) PyArray_FromDims(nd, dims, NPY_DOUBLE);
  a = pyvector_to_Carrayptrs(alpha);
  b = pyvector_to_Carrayptrs(beta);
  int ierr;

  drecur_(n, ipoly, al, be, a, b, ierr);

  return Py_BuildValue("OO", alpha, beta);
}

double *pyvector_to_Carrayptrs(PyArrayObject *arrayin)  {
  int n=arrayin->dimensions[0];
  return (double *) arrayin->data;  /* pointer to arrayin data as double */
}

随时对此发表评论,并感谢您的回答。

4

2 回答 2

3

因此,看起来可疑的第一件事是您的数组a并且b在函数的本地范围内。这意味着在返回后您将获得非法的内存访问。

所以你应该用

double *a = malloc(n*sizeof(double));

然后,您需要确保稍后您创建的对象释放了内存。请参阅文档的此引用:

PyObject PyArray_SimpleNewFromData(int nd, npy_intp dims, int typenum, void* data)

有时,您希望将在其他地方分配的内存包装到 ndarray 对象中以供下游使用。这个例程使它很容易做到这一点。前三个参数与 PyArray_SimpleNew 中的相同,最后一个参数是指向 ndarray 应该用作其数据缓冲区的连续内存块的指针,它将以 C 风格的连续方式解释。返回对 ndarray 的新引用,但 ndarray 不会拥有它的数据。当这个 ndarray 被释放时,指针不会被释放。

您应该确保在返回的数组存在时不释放提供的内存。处理此问题的最简单方法是数据是否来自另一个引用计数的 Python 对象。传入指针后,该对象的引用计数应增加,返回的 ndarray 的基成员应指向拥有数据的 Python 对象。然后,当 ndarray 被释放时,base-member 将被适当地 DECREF'd。如果您希望在释放 ndarray 后立即释放内存,则只需在返回的 ndarray 上设置 OWNDATA 标志。

对于您的第二个问题,Py_INCREF(alpha);通常仅当您打算将引​​用保留在全局变量或类成员中时才需要。但是由于您只是包装一个函数,因此您不必这样做。可悲的是,该函数可能PyArray_SimpleNewFromData没有将引用计数器设置为 1,如果是这种情况,您将不得不将其增加到 1。我希望这是可以理解的;)。

于 2012-09-25T12:55:58.103 回答
1

一个问题可能是您的数组 (a,b) 必须至少与包含它的 numpy-array 一样长。您已经在本地范围内创建了数组,因此当您离开该方法时它们将被销毁。

尝试让python分配数组(例如使用PyArray_SimpleNew),将您的内容复制到其中并传递一个指针。如果可以选择针对 boost 构建,您可能还想使用boost::python来处理这些细节。

于 2012-09-25T12:32:32.240 回答