Python 将三维numpy数组传递给C

Python 将三维numpy数组传递给C,python,c,pointers,numpy,python-c-extension,Python,C,Pointers,Numpy,Python C Extension,为了提高速度,我正在为我的Python程序编写一个C扩展,并且在尝试传入三维numpy数组时遇到了一些非常奇怪的行为。它适用于二维数组,但我敢肯定,为了让它适用于三维数组,我用指针把它搞砸了。但奇怪的是。如果我只是传入一个3-D数组,它会因总线错误而崩溃。如果(在Python中)我首先将变量创建为2D数组,然后用3D数组覆盖它,它可以完美地工作。如果变量先是空数组,然后是3D数组,则会因Seg故障而崩溃。这怎么可能发生呢 还有,有人能帮我让3D阵列工作吗?或者我应该放弃,通过一个2D数组,自己重

为了提高速度,我正在为我的Python程序编写一个C扩展,并且在尝试传入三维numpy数组时遇到了一些非常奇怪的行为。它适用于二维数组,但我敢肯定,为了让它适用于三维数组,我用指针把它搞砸了。但奇怪的是。如果我只是传入一个3-D数组,它会因总线错误而崩溃。如果(在Python中)我首先将变量创建为2D数组,然后用3D数组覆盖它,它可以完美地工作。如果变量先是空数组,然后是3D数组,则会因Seg故障而崩溃。这怎么可能发生呢

还有,有人能帮我让3D阵列工作吗?或者我应该放弃,通过一个2D数组,自己重塑它吗

这是我的C代码:

static PyObject* func(PyObject* self, PyObject* args) {
  PyObject *list2_obj;
  PyObject *list3_obj;
  if (!PyArg_ParseTuple(args, "OO", &list2_obj, &list3_obj))
    return NULL;

  double **list2;
  double ***list3;

  //Create C arrays from numpy objects:
  int typenum = NPY_DOUBLE;
  PyArray_Descr *descr;
  descr = PyArray_DescrFromType(typenum);
  npy_intp dims[3];
  if (PyArray_AsCArray(&list2_obj, (void **)&list2, dims, 2, descr) < 0 || PyArray_AsCArray(&list3_obj, (void ***)&list3, dims, 3, descr) < 0) {
    PyErr_SetString(PyExc_TypeError, "error converting to c array");
    return NULL;
  }
  printf("2D: %f, 3D: %f.\n", list2[3][1], list3[1][0][2]);
}
所以,如果我同时注释掉A行和B行,它会因为总线错误而崩溃。如果A行在那里,但是B行被注释掉了,那么它运行正确,没有错误。如果有B行,但A行被注释掉,则会打印正确的数字,但随后会出现Seg故障。最后,如果两条线路都存在,它也会打印正确的数字,然后显示Seg故障。这到底是怎么回事

编辑:确定。哇!因此,我在Python中使用了
int
,但在C中称它们为
double
,这在1D和2D数组中运行良好。但不是3D。因此,我将l3的Python定义更改为float,现在它的工作非常出色(非常感谢您)

但现在,更奇怪的行为与线A和B!现在,如果两行都被注释掉,程序就可以运行了。如果行B存在,但A被注释掉,则该行有效,如果两者都未注释,则同上。但如果A行出现,B被注释掉,我会再次得到那个奇妙的总线错误。我真的希望在将来避免这些,那么有人知道为什么Python变量的声明会产生这种影响吗

编辑2:虽然这些错误很疯狂,但它们都是由于我传入的三维numpy数组造成的。若我只传入1-D或2-D数组,它的行为和预期的一样,而对其他Python变量的操作并没有任何作用。这使我相信问题在于Python的引用计数。在C代码中,引用计数的减少幅度大于3-D数组的减少幅度,当该函数返回时,Python尝试清理对象,并尝试删除空指针。这只是我的猜测,我试着
Py_INCREF()我能想到的一切都没有用。我想我将使用一个2D数组,并用C对其进行重塑。

根据:

注意:对于二维和三维阵列,C型阵列的模拟并不完整。例如,指针的模拟数组不能传递给需要特定的、静态定义的二维和三维数组的子例程。要传递给需要此类输入的函数,必须静态定义所需的数组并复制数据

我认为这意味着
PyArray\u AsCArray
返回一个内存块,其中的数据按C顺序排列。但是,要访问该数据,需要更多信息(请参阅)。这可以通过提前知道维度、声明数组,然后按正确的顺序复制数据来实现。然而,我怀疑更一般的情况更有用:在返回维度之前,您不知道它们。我认为下面的代码将创建必要的C指针框架,以允许对数据进行寻址

static PyObject* func(PyObject* self, PyObject* args) {
    PyObject *list2_obj;
    PyObject *list3_obj;
    if (!PyArg_ParseTuple(args, "OO", &list2_obj, &list3_obj)) return NULL;

    double **list2;
    double ***list3;

    // For the final version
    double **final_array2;
    double **final_array2;

    // For loops
    int i,j;

    //Create C arrays from numpy objects:
    int typenum = NPY_DOUBLE;
    PyArray_Descr *descr;
    descr = PyArray_DescrFromType(typenum);

    // One per array coming back ...
    npy_intp dims2[2];
    npy_intp dims3[3];

    if (PyArray_AsCArray(&list2_obj, (void **)&list2, dims2, 2, descr) < 0 || PyArray_AsCArray(&list3_obj, (void ***)&list3, dims3, 3, descr) < 0) {
        PyErr_SetString(PyExc_TypeError, "error converting to c array");
        return NULL;
    }

    // Create the pointer arrays needed to access the data

    // 2D array
    final_array2 = calloc(dim2[0], sizeof(double *));
    for (i=0; i<dim[0]; i++) final_array2[i] = list2 + dim2[1]*sizeof(double);

    // 2D array
    final_array3    = calloc(dim3[0], sizeof(double **));
    final_array3[0] = calloc(dim3[0]*dim3[1], sizeof(double *));
    for (i=0; i<dim[0]; i++) {
         final_array3[i] = list2 + dim3[1]*sizeof(double *);
         for (j=0; j<dim[1]; j++) {
             final_array[i][j] = final_array[i] + dim3[2]*sizeof(double);
         }
    }

    printf("2D: %f, 3D: %f.\n", final_array2[3][1], final_array3[1][0][2]);
    // Do stuff with the arrays

    // When ready to complete, free the array access stuff
    free(final_array2);

    free(final_array3[0]);
    free(final_array3);

    // I would guess you also need to free the stuff allocated by PyArray_AsCArray, if so:
    free(list2);
    free(list3);
}
static PyObject*func(PyObject*self,PyObject*args){
PyObject*list2_obj;
PyObject*list3_obj;
if(!PyArg_ParseTuple(args,“OO”&list2_obj和list3_obj))返回NULL;
双**列表2;
双***列表3;
//最终版本
双**最终阵列2;
双**最终阵列2;
//For循环
int i,j;
//从numpy对象创建C数组:
int-typenum=NPY\u-DOUBLE;
PyArray_Descr*Descr;
descr=PyArray\u DescrFromType(typenum);
//每个阵列返回一个。。。
npy_intp dims2[2];
npy_intp dims3[3];
if(PyArray_AsCArray(&list2_obj,(void**)和list2,dims2,2,descr)<0 | | PyArray_AsCArray(&list3_obj,(void***)和list3,dims3,3,descr)<0){
PyErr_SetString(PyExc_TypeError,“错误转换为c数组”);
返回NULL;
}
//创建访问数据所需的指针数组
//二维阵列
final_array2=calloc(dim2[0],sizeof(double*);

对于(i=0;i,我通常使用
PyArray\u GETPTR
直接访问numpy数组元素,而不是转换为c样式的数组(请参阅)

例如,要访问double类型的三维numpy数组的元素,请使用
double elem=*((double*)PyArray\u GETPTR3(list3\u obj,i,j,k))


对于您的应用程序,您可以使用
PyArray\u NDIM
检测每个数组的正确维数,然后使用适当版本的
PyArray\u GETPTR

访问元素。我已经在一篇评论中提到了这一点,但我希望将其清除一点有助于使其更加清晰

在C语言中使用numpy数组时,最好明确数组的类型。具体来说,它看起来像是将指针声明为
double***list3
,但它们与您在python代码中创建
l3
的方式相同,您将得到一个dtype
npy\u intp
的数组(我认为)。您可以通过在创建阵列时显式使用数据类型来解决此问题

import cmod, numpy
l2 = numpy.array([[1.0,2.0,3.0],
                  [4.0,5.0,6.0],
                  [7.0,8.0,9.0],
                  [3.0, 5.0, 0.0]], dtype="double")

l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]],
                  [[1, 10, 13, 15], [4, 2, 6, 2]]], dtype="double")

cmod.func(l2, l3)
另一个注意事项是,由于python的工作方式,“A行”和“B行”几乎不可能对C代码产生任何影响。我知道这似乎与您的经验相冲突,但我非常肯定这一点

我对此不太确定,但根据我在C语言方面的经验,总线错误和SEGFULT不是确定性的,它们取决于
import cmod, numpy
l2 = numpy.array([[1.0,2.0,3.0],
                  [4.0,5.0,6.0],
                  [7.0,8.0,9.0],
                  [3.0, 5.0, 0.0]], dtype="double")

l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]],
                  [[1, 10, 13, 15], [4, 2, 6, 2]]], dtype="double")

cmod.func(l2, l3)