使用Cython将从C函数创建的2D数组返回到Python

时间:2018-03-28 15:54:32

标签: python c cython

我想在c中使用由python函数创建的2D数组。我在today之前询问了如何做到这一点,而@Abhijit Pritam提出的一种方法是使用结构。我实现了它,它确实有效。

c code:

typedef struct {
  int arr[3][5];
} Array;

Array make_array_struct() {
  Array my_array;
  int count = 0;
  for (int i = 0; i < 3; i++)
    for (int j = 0; j  < 5; j++)
      my_array.arr[i][j] = ++count;
  return my_array;
}
在python中我有这个:

cdef extern from "numpy_fun.h":
    ctypedef struct Array:
        int[3][5] arr
    cdef Array make_array_struct()

def make_array():
    cdef Array arr = make_array_struct()
    return arr

my_arr = make_array()
my_arr['arr']
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]

然而有人认为这不是解决问题的最佳方法,因为它可以使python控制数据。我试图实现这一点,但到目前为止我还没能做到这一点。这就是我所拥有的。

c code:

int **make_array_ptr() {
  int **my_array = (int **)malloc(3 * sizeof(int *));
  my_array[0] = calloc(3 * 5, sizeof(int));
  for (int i = 1; i < 3; i++)
    my_array[i] = my_array[0] + i * 5;
  int count = 0;
  for (int i = 0; i < 3; i++)
    for (int j = 0; j < 5; j++)
      my_array[i][j] = ++count;
  return my_array;
}

蟒:

import numpy as np
cimport numpy as np

np.import_array()

ctypedef np.int32_t DTYPE_t

cdef extern from "numpy/arrayobject.h":
    void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)

cdef extern from "numpy_fun.h":
    cdef int **make_array_ptr()

def make_array():
    cdef int[::1] dims = np.array([3, 5], dtype=np.int32)
    cdef DTYPE_t **data = <DTYPE_t **>make_array_ptr()
    cdef np.ndarray[DTYPE_t, ndim=2] my_array = np.PyArray_SimpleNewFromData(2, &dims[0], np.NPY_INT32, data)
    PyArray_ENABLEFLAGS(my_array, np.NPY_OWNDATA)
    return my_array

我正在关注Force NumPy ndarray to take ownership of its memory in Cython 这似乎是我需要做的事情。在我的情况下,它是不同的,因为我需要2D数组,所以我可能不得不做一些不同的事情,因为例如函数期望data是指向int的指针,我给它指向int的指针。 我该怎么做才能使用这种方法?

1 个答案:

答案 0 :(得分:2)

我对struct方法的问题是:

  1. 只要你想要一个固定大小的数组,它就会中断,没有真正的修复方法。

  2. 它依赖于Cython从结构到dicts的隐式转换。 Cython将数据复制到Python列表,这不是非常有效。这不是你在这里使用的小阵列的问题,但对于更大的阵列来说这是愚蠢的。

  3. 我也不是真的推荐2D数组作为指针指针。 numpy(以及大多数其他敏感数组库)实现2D数组的方式是存储一维数组和二维数组的形状,并使用该形状来计算要访问的索引。这往往更有效(更快的查找,更快的分配),也更容易使用(更少的分配/释放来跟踪)。

    要执行此操作,请将C代码更改为:

    int32_t *make_array_ptr() {
      int32_t *my_array = calloc(3 * 5, sizeof(int32_t));
      int count = 0;
      for (int i = 0; i < 3; i++)
        for (int j = 0; j < 5; j++)
          my_array[j+i*5] = ++count;
      return my_array;
    }
    

    我删除了您立即覆盖的第一个循环。我也改变了int32_t的类型,因为你似乎在以后的Cython代码中依赖它。

    Cython代码非常接近您使用的代码:

    def make_array():
        cdef np.intp_t dims[2] 
        dims[0]=3; dims[1] = 5
        cdef np.int32_t *data = make_array_ptr()
        cdef np.ndarray[np.int32_t, ndim=2] my_array = np.PyArray_SimpleNewFromData(2, &dims[0], np.NPY_INT32, data)
        PyArray_ENABLEFLAGS(my_array, np.NPY_OWNDATA)
        return my_array
    

    主要的变化是我删除了一些演员表,并且还将dims分配为静态数组(看起来比memoryviews简单)

    我不认为让numpy处理指向指针数组变得特别容易。可能通过实现Python缓冲区接口,但这似乎很多工作,可能并不容易。