Cython:将一个二维NumPy数组传递给cdef函数

5
我想将一个二维numpy数组传递给一个cdef函数,其中数组的维度可以变化。以下是我的尝试:
cimport numpy as cnp

input = numpy.array([[3.34, 2.2],[1.1, -0.6]])
input = input[:,:].astype(np.double)
cdef int nrows = 2
cdef int ncols = 2

# output of function
cdef cnp.ndarray[cnp.uint8_t, ndim=2] output = np.zeros((2,2), dtype=np.uint8)  

test_array(nrows, ncols, &input[0], <unsigned char**>output.data)

我的 test_array 函数在哪里:

cdef void test_array(Py_ssize_t nrows, Py_ssize_t ncols, double **x, unsigned char **output) nogil:

output[0][0]=1
output[1][0]=0
output[1][1]=1
output[0][1]=0

我的函数原型是:

cdef void test_array(Py_ssize_t nrows, Py_ssize_t ncols, double **x, unsigned char **output) nogil

当我编译时,出现错误提示“无法获取Python对象的地址”,并指向&input[0]。这种语法适用于一维数组,但我不确定二维数组的语法是什么。我也尝试过&input[0][0],但那也是错误的。

你有没有可能创建一个Gist或小型仓库,其中包含你正在尝试编译的文件的确切内容? - ngoldbaum
一个二维数组的第一个元素是[0,0] - Pierre de Buyl
我看到你想传递一个指向 input 的指针。这对于 Cython 的 C 部分来说是未知的,因此您必须将数据作为 typed memoryview 传递。如果您将 NumPy 数组传递给需要 typed memoryview 的函数,则它将按预期运行 :-) - Pierre de Buyl
1个回答

7

不清楚您想要达到什么目的:

A: 如果应该是一个纯Cython函数,那么您应该使用类型化内存视图,这意味着您的函数签名应该是

cdef void test_array(double[:,:] x, unsigned char[:,:] output) nogil:

没有 nrowsncols 是因为类型化的内存视图已经包含了这些信息(类似于 std::vector)。

B: array_test 实际上是一个 c 函数的包装器,该函数期望 double **unsigned char **,您应该查看这个 SO-question


实际上,我想解释一下为什么您的尝试没有成功。

首先,为什么 &input[0] 没有起作用?真正的问题是 input[0] 是什么:

import numpy as np
input=np.zeros((3,3))
type(input[0])
<type 'numpy.ndarray'>
type(input[:,0])
<type 'numpy.ndarray'>
type(input[0,0])
<type 'numpy.float64'>

所以input是一个表示Python对象的numpy.ndarray,而Cython拒绝获取它的地址。对于input [0,0]同样如此-它是一个Python对象。到目前为止没什么运气。

要使其正常工作,您需要将input转换为Cython-Numpy数组(我不知道如何更好地表达它-请看示例):

import numpy as np
cimport numpy as np #that the way it is usually imported

def try_me():
    cdef np.ndarray[double, ndim=2] input = np.array([[3.34, 2.2],[1.1, -0.6]])
    cdef double *ptr1=&input[0,0]
    cdef double *ptr2=&input[1,0]
    print ptr1[0], ptr2[1] #prints 3.34 and -0.6

重要部分是:`input`不再被视为Python对象,而是被视为cython类型的`np.ndarray [double,ndim = 2]`,这就是使语法`&input [0,0]`首次成为可能的原因。
也许更准确的看法是:`cimport numpy`为我们提供了处理numpy数组的其他工具,因此我们可以访问在纯python中无法访问的内部。
然而,`&input [0,0]`不是类型为`double **`而是类型为`double *`,因为`numpy.ndarray`只是一块连续的内存,而只有运算符`[i,j]`模拟了2d的感觉。
How it feels:
    A[0] -> A00 A01 A02
    A[1] -> A10 A11 A12

The real layout in the memory:
     A00 A01 A02 A10 A11 A12

没有行指针,但是你可以通过 cdef double *ptr2=&input[row_id,0] 创建它们,如何处理可在上述问题中讨论。
numpy.ndarray 只是一段连续的内存是简化了 - numpy.ndarray 是一个相当复杂的实体!请考虑以下示例:
import numpy as np
cimport numpy as np

def try_me2():
    cdef np.ndarray[double, ndim=2] input = np.array([[1.0, 2.0],
                                                      [3.0, 4.0]])
    cdef np.ndarray[double, ndim=1] column = input[:,1]  
    cdef double *ptr = &column[0]
    print column          #prints column "2 4"
    print ptr[0],ptr[1]   #prints "2 3" and not "2 4"!

现在,inputcolumn共享同一个内存空间,在该内存中,input[0][1]=column[0]之后才保存了input[1][0],然后才是input[1][1]=column[1]ptr[1]占据了input[0][1]的下一个内存单元,这是input[1][0]=3而不是input[1][1]=4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接