我正在为我的Python程序编写C扩展以提高速度,并且在尝试传递一个三维numpy数组时遇到了一些非常奇怪的行为。它可以使用二维数组,但我确信在尝试使用第三个维度时出了问题。但是这里有一个奇怪的部分,如果我只传入一个三维数组,它会崩溃并显示总线错误。 如果(在Python中)我首先将变量创建为2D数组,然后用3D数组覆盖它,它完美地工作。如果变量首先为空数组,然后是3D数组,则会崩溃并显示段错误。这怎么可能发生?
此外,有人能帮助我使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行被注释掉,它会输出正确的数字但随后出现段错误。最后,如果两行都存在,它也会输出正确的数字并然后发生段错误。这到底是怎么回事?
编辑:好的,哇,所以我在Python中使用了int
,但在C中调用它们时使用了double
。对于1D和2D数组,这很好用,但不适用于3D。所以我改变了Python的l3定义为浮点数,现在一切都工作得很好(非常感谢Bi Rico)。
但现在,使用A和B两行出现了更奇怪的行为!如果两行都被注释掉,程序就可以正常工作。如果B行存在而A行被注释掉,它也可以正常工作,如果两行都未被注释掉,则同样如此。但是如果A行存在但B行被注释掉,我又得到了那个奇妙的总线错误。我真的很想避免这种问题,那么有人知道为什么Python变量的声明会产生这种影响吗?
编辑2:嗯,尽管这些错误很疯狂,但它们都是由我传递的3维numpy数组引起的。如果只传递1或2维数组,则行为符合预期,并且对其他Python变量的操作没有任何影响。这使我认为问题出在Python的引用计数上。在C代码中,3D数组的引用计数减少得比应该多,当函数返回时Python试图清除对象,并尝试删除一个空指针。这只是我的猜测,我已经尝试过尽可能多地Py_INCREF();
,但无济于事。我想我只能使用2D数组并在C中重新调整形状。
(void **)
是正确的吗?难道不应该只传入(void*)
吗? - sebergPyArray_AsCArray
成功,你在if
中的表达式不是会短路吗?很可能第二次调用,也就是list3
的那个,根本没有执行。 - Jaime(void **)
是否正确,但使用(void*)
会导致总线错误。 @Jaime 不,该函数仅在失败时返回负值,最有可能是它调用的malloc失败了。 - DaveTheScientist