为了速度目的,我正在为我的Python程序编写一个C扩展,并试图传入一个三维的numpy数组中遇到一些非常奇怪的行为。它适用于一个二维数组,但我确定我正在搞砸一些指针试图让它与第三维一起工作。但这是奇怪的部分。如果我只是传入一个三维数组,它会因总线错误而崩溃。如果(在Python中)我首先将我的变量创建为2D数组,然后用3D数组覆盖它,它完美地运行。如果变量首先是空数组,然后是3D数组,则会以 Seg Fault 崩溃。怎么可能发生这种情况?
另外,有人可以帮我搞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]);
}
这是我调用上述函数的Python代码:
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]])
l3 = numpy.array([[2,7, 1], [6, 3, 9], [1, 10, 13], [4, 2, 6]]) # Line A
l3 = numpy.array([]) # Line B
l3 = numpy.array([[[2,7, 1, 11], [6, 3, 9, 12]],
[[1, 10, 13, 15], [4, 2, 6, 2]]])
cmod.func(l2, l3)
所以,如果我注释掉A行和B行,它会因总线错误而崩溃。如果有A行,但是B行被注释掉,它会正确运行而没有错误。如果线路B在那里但是线路A被注释掉,它会输出正确的数字但是会出现Seg故障。最后,如果两条线都存在,它还会打印正确的数字,然后是Seg故障。到底是怎么回事?
编辑:好的。哇。所以我在Python中使用int
但在C中使用double
。这对于1D和2D数组工作正常。但不是3D。因此我将l3的Python定义更改为浮点数,现在这一切都非常有效(非常感谢Bi Rico )。
但现在,线条A&amp; amp;更奇怪的行为乙!现在如果两行都被注释掉,程序就可以运行了。如果存在B行但是A被注释掉,则它可以工作,如果两个都被取消注释,则同上。但是如果存在A行并且B被注释掉,我再次得到了那个梦幻般的总线错误。我真的很想在将来避免这些,所以有没有人知道为什么Python变量的声明会产生这种影响?
编辑2:嗯,就像这些错误一样疯狂,它们都归因于我传入的3维numpy数组。如果我只传入1或2-D数组,它的行为与预期一致,并且对其他Python变量的操作不起作用。这让我相信问题出在Python的引用计数中。在C代码中,引用计数比3-D数组的数量减少得多,当该函数返回时,Python尝试清理对象,并尝试删除NULL指针。这只是我的猜测,我试图Py_INCREF();
我能想到的一切都无济于事。我想我只是使用2D数组并在C中重塑它。
答案 0 :(得分:3)
我通常使用PyArray_GETPTR
直接访问numpy数组元素而不是转换为c风格的数组(参见http://docs.scipy.org/doc/numpy/reference/c-api.array.html#data-access)。
例如,访问类型为double的3维numpy数组的元素
double elem=*((double *)PyArray_GETPTR3(list3_obj,i,j,k))
。
对于您的应用,您可以使用PyArray_NDIM
检测每个阵列的正确维度数,然后使用相应版本的PyArray_GETPTR
访问元素。
答案 1 :(得分:3)
我在评论中已经提到过这个问题,但我希望将其简化一下,有助于使其更清晰。
当您在C中使用numpy数组时,最好明确指出数组的类型。具体来说,看起来你正在将你的指针声明为double ***list3
,但是你在python代码中创建l3
,你会得到一个dtype为npy_intp
的数组(我认为) 。您可以通过在创建数组时显式使用dtype来解决此问题。
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的经验,总线错误和段错误不是确定性的。它们依赖于内存分配,对齐和地址。在某些情况下,代码似乎运行了10次,并且在第11次运行时失败,即使没有任何改变。
您是否考虑过使用cython?我知道这不是每个人的选择,但是如果它是一个选项,你可以使用typed memoryviews获得近乎C级的加速。
答案 2 :(得分:1)
注意对于2维和3维阵列,C样式阵列的模拟不完整。例如,模拟的指针数组不能传递给期望特定的,静态定义的2-d和3-d数组的子程序。要传递给需要这些输入的函数,必须静态定义所需的数组并复制数据。
我认为这意味着PyArray_AsCArray
返回一个内存块,其中的数据按C顺序排列。但是,要访问该数据,需要更多信息(请参阅http://www.phy225.dept.shef.ac.uk/mediawiki/index.php/Arrays,_dynamic_array_allocation)。这可以通过提前知道维度,声明数组,然后以正确的顺序复制数据来实现。但是,我怀疑更一般的情况更有用:在返回之前你不知道尺寸。我认为以下代码将创建必要的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);
}
我找不到npy_intp
的定义,上面假设它与int
相同。如果不是,则需要在执行代码之前将dim2
和dim3
转换为int
数组。
答案 3 :(得分:1)
numpy C-API中存在一个错误,现在应该修复: