C массив для PyArray

Я пишу C-Extension Python без использования Cython.

Я хочу выделить двойной массив в 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);
}

Я отлаживал этот код, и я получаю ошибку сегментации, когда пытаюсь создать альфа из. Там все работает нормально. Функция drecur_ работает, и я получаю ту же проблему, если она удалена.

Теперь, каков стандартный способ определения PyArray вокруг данных C? Я нашел документацию, но не был хорошим примером. Также, как насчет утечки памяти? Правильно ли это для INCREF перед возвратом, чтобы сохранить экземпляр альфы и бета? Как насчет освобождения, когда они больше не нужны?

ИЗМЕНИТЬ Я, наконец, понял это с помощью подхода, найденного в кулинарной книге NumPy.

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 */
}

Не стесняйтесь прокомментировать это и спасибо за ответы.

Ответ 1

Итак, первое, что выглядит подозрительно, заключается в том, что ваш массив 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 будет освобожден, базовый элемент будет DECREFd соответствующим образом. Если вы хотите освободить память, как только ndarray будет освобожден, просто установите флаг OWNDATA в возвращаемом ndarray.

Для вашего второго вопроса Py_INCREF(alpha); обычно необходим только в том случае, если вы намерены сохранить ссылку в глобальной переменной или члене класса. Но поскольку вы только обертываете функцию, вам это не нужно. К сожалению, может быть, что функция PyArray_SimpleNewFromData не устанавливает контрольный счетчик равным 1, если это будет так, вы должны увеличить его до 1. Надеюсь, это было понятно;).

Ответ 2

Одна из проблем может заключаться в том, что ваши массивы (a, b) должны длиться, по крайней мере, до тех пор, пока массив numpy-array, содержащий его. Вы создали свои массивы в локальной области, поэтому они будут уничтожены при выходе из метода.

Попробуйте назначить python массив (например, используя PyArray_SimpleNew), скопируйте его содержимое и передайте ему указатель. Вы также можете использовать boost:: python для того, чтобы заботиться об этих деталях, если опция против boost является опцией.