cython:将二维 numpy 数组传递给 cdef 函数

use*_*489 4 python arrays numpy cython multidimensional-array

我想将一个 2D 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)
Run Code Online (Sandbox Code Playgroud)

我的 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
Run Code Online (Sandbox Code Playgroud)

我的函数原型是:

cdef void test_array(Py_ssize_t nrows, Py_ssize_t ncols, double **x, unsigned char **output) nogil
Run Code Online (Sandbox Code Playgroud)

编译时,我收到一条错误消息,提示“无法获取 Python 对象的地址”并指向&input[0]. 该语法适用于一维数组,但我不确定二维数组的语法是什么。我也试过,&input[0][0]但那也是错误的。

ead*_*ead 5

目前尚不清楚您想要实现的目标:

A:如果它应该是一个纯 cython 函数,那么你应该使用typed memory view,这意味着你的函数签名应该是

cdef void test_array(double[:,:] x, unsigned char[:,:] output) nogil:
Run Code Online (Sandbox Code Playgroud)

没有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'>
Run Code Online (Sandbox Code Playgroud)

所以input是一个numpy.ndarray,这意味着一个Python对象,并用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
Run Code Online (Sandbox Code Playgroud)

重要的部分:input不再被视为/解释为 python 对象,而是作为 cython-type 类型np.ndarray[double, ndim=2],这首先使语法成为&input[0,0]可能。

也许有一种更精确的方式来看待它:cimport numpy为我们提供了处理 numpy 数组的额外工具,以便我们可以访问在纯 python 中无法访问的内部结构。

然而,&input[0,0]不是 typedouble **而是 type 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
Run Code Online (Sandbox Code Playgroud)

没有指向行的指针,但您可以通过创建它们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"!
Run Code Online (Sandbox Code Playgroud)

现在,这里inputcolumn共享相同的内存并且在内存input[1][0]中保存之后input[0][1]=column[0]并且只有那么input[1][1]=column[1]ptr[1]将内存单元放在旁边input[0][1],这是input[1][0]=3不是input[1][1]=4