如何正确地将Numpy数组传递给Cython函数?

14

这个问题在很多地方都有描述,但我就是无法让它工作。我正在从Cython调用一个C++函数:

cimport numpy as np
cdef extern from "test.h" namespace "mytest":
   void test(double *A, int m)

cdef int foo():
  cdef np.ndarray[double,mode="c"] a = np.array([1,2,3,4,5],dtype=float)
  # pass ptr to first element of 'a'
  test(&a[0], len(a))
  return 0

foo()

test.cpp 就是:

#include <stdio.h>
namespace mytest {
    void test(double *A, int m)
    {
    for (int i = 0; i < m; i++)
    {
        printf("%d is %f\n", i, A[i]);
    }
    }
}

test.h文件仅包含:

namespace mytest {
  void test(double *A, int m);
}

这似乎可行,但什么时候需要使用np.ascontiguousarray?只做以下操作是否足够:

cdef np.ndarray[double,mode="c"] a = np.array([1,2,3,4,5],dtype=float)

或者你需要:

cdef np.ndarray[double,mode="c"] a = np.ascontiguousarray(np.array([1,2,3,4,5],dtype=float))

其次,更重要的是,如何将此推广到2维数组?

处理2维数组

以下是我尝试将2维numpy数组传递给不起作用的C++代码:

cdef np.ndarray[double,mode="c",ndim=2] a = np.array([[1,2],[3,4]],dtype=float)

这被称为:

test(&a[0,0], a.shape[0], a.shape[1])

在 CPP 代码中:

void test(double *A, int m, int n) 
{ 
  printf("reference 0,0 element\n");
  printf("%f\n", A[0][0]);
}

更新:

正确的答案是对数组使用线性索引,而不是 [][] 语法。打印二维数组的正确方式是:

for (int i = 0; i < m; i++)
{
    for (int j = 0; j < n; j++)
    {
    printf("%d, %d is %f\n", i, j, A[i*m + j]);
    }
}

1
在你的二维示例中,我觉得你对指针A进行了两次解引用。对于一个二维数组,你可能需要手动进行索引算术运算。例如,如果你有一个C连续的m x n数组,并且你想要做NumPy的A[i,j]的C等价操作,你需要做的是A[m*i+j]而不是A[0][0]。两次解引用指针可能会导致Python崩溃。 - IanH
1
m是行数。当您为指针指定类型时,数据类型会得到处理。我将举一个快速的例子。 - IanH
@IanH:我现在明白了,我更新了我的答案,并提供了一个可行的示例供未来用户参考。 - user248237
1
@user248237dfsf:我喜欢这个线程中使用typed memoryviews的建议(使用&s[0]语法传递给C)。 - jfs
显示剩余2条评论
1个回答

6

对于二维数组,您只需要使用 ndim 关键字:

cdef np.ndarray[double, mode="c", ndim=2]

结果可能与原始数据共享内存,如果共享内存,则数组可能不连续或具有异常的步幅配置。在这种情况下,直接将缓冲区传递给C/C++将是灾难性的。
除非您的C/C++代码已准备好处理非连续数据(在这种情况下,您需要将所有相关步幅数据从Cython传递到C函数中),否则应始终使用ascontiguousarray。如果输入数组已经连续,将不会进行复制。确保向ascontiguousarray传递兼容的dtype,以避免第二次复制(例如,必须从连续的float数组转换为连续的double数组)。

@nneoneo:我有ndim,但在原始帖子中打错了,抱歉。添加ndim=2是必要的以创建数组,但我仍然不知道如何从C端访问它。你能给个例子吗? - user248237
哦,我明白问题了。在C语言中,除非你有一个double **(指针数组)或者一个double [][](声明的二维数组),否则你不能像那样进行双重索引。否则,只有一个double *,你必须手动索引:A[i*n + j] - nneonneo
@nneoneo:你能说一下这里的“double **”是如何工作的吗?正如你所说,我的数组在Cython端被声明为2D。这是否意味着我的函数可以有一个类型为“double **”的参数,然后使用“[][]”进行索引?还是我需要先将其转换为指针数组?这是我从手册中不理解的地方。 - user248237
@user248237dfsf:你没有指针数组(你只有一个平面数据数组),所以你不能使用double **。(这种情况通常是当你有一个指向每行开头的间接指针数组时)。无论你有多少维,Cython都不会给你double **;你总是得到double * - nneonneo
2
在C语言中,你必须使用一维索引。这在处理连续数据缓冲区时很常见。(对于多维的C数组,比如double [][],C编译器会根据需要将数组维度与索引相乘,从而生成等效的一维索引操作)。 - nneonneo
显示剩余4条评论

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