Python C扩展问题:无法将浮点值存入数组(类型不匹配错误)
问题:Python C扩展中PyFloat_AsDouble调用报错,PyLong_AsLong却正常
正在学习C语言,尝试编写Python C扩展实现COO稀疏数据到稠密数组的转换(已知SciPy可实现该功能),但遇到阻碍。能通过PyLong_AsLong获取整数列表中的值,却无法用PyFloat_AsDouble获取浮点列表中的值,不清楚两者的差异导致报错的原因。
报错信息
motion_tools.c:20:68: error: incompatible type for argument 1 of 'PyFloat_AsDouble' 20 | double v = (double) PyFloat_AsDouble(val_float_list[i]); | ~~~~~~~~~~~~~~^~~ | | | PyObject {aka struct _object} In file included from /usr/include/python3.10/Python.h:87, from motion_tools.c:2: /usr/include/python3.10/floatobject.h:49:37: note: expected 'PyObject *' {aka 'struct _object *'} but argument is of type 'PyObject' {aka 'struct _object'} 49 | PyAPI_FUNC(double) PyFloat_AsDouble(PyObject *); | ^~~~~~~~~~
测试代码(tool_test.py)
# tool_test.py import motion_tools motion_tools.sparse_to_dense([0,1],[0,1],[1.0,1.0],(2,2))
C扩展代码(motion_tools.c)
// motion_tools.c #define PY_SSIZE_T_CLEAN #include <Python.h> #include <stdio.h> PyObject *sparse2dense(PyObject* self, PyObject* args) { PyObject* x_int_list; // 传入的Python列表 PyObject* y_int_list; // 传入的Python列表 PyObject* val_float_list; // 传入的Python列表 PyObject* shape_int_tuple; // 传入的Python元组 if (!PyArg_ParseTuple(args, "OOOO", &x_int_list, &y_int_list, &val_float_list, &shape_int_tuple)) return NULL; PyObject *out_array = PyArray_Zeros(2, shape_int_tuple); Py_ssize_t element_count = PyList_Size(x_int_list); int i; for (i=0; i<element_count; ++i){ int x = (int) PyLong_AsLong(x_int_list[i]); // 此代码可正常运行 int y = (int) PyLong_AsLong(y_int_list[i]); // 此代码可正常运行 double v = (double) PyFloat_AsDouble(val_float_list[i]); // 此代码运行报错 //out_array[y][x] = v; PyArray_SETITEM(out_array, PyArray_GETPTR2(out_array, y, x), v); } return PyArray_Return(out_array); } static PyModuleDef methods[] = { { "sparse_to_dense", sparse2dense, METH_VARARGS, "将COO格式转换为稠密数组"}, { NULL, NULL, 0, NULL } }; static struct PyModuleDef sparse_to_dense = { PyModuleDef_HEAD_INIT, "motion_tools", "稀疏转稠密模块", -1, methods }; PyMODINIT_FUNC PyInit_motion_tools(void) { return PyModule_Create(&sparse_to_dense); }
问题原因与修复
核心原因
报错本质是错误地用C数组下标语法访问Python列表对象:x_int_list[i]、val_float_list[i]这种写法在C中是访问数组元素,但x_int_list是PyObject*(指向Python列表的指针),不是C数组。这种写法会直接解引用指针偏移后的内存,得到的是PyObject类型(结构体实例),而PyLong_AsLong和PyFloat_AsDouble都要求参数是PyObject*(结构体指针),因此类型不匹配导致报错。之前PyLong_AsLong没报错可能是编译器的巧合,实际上同样存在问题。
修复方案
必须使用Python C API提供的PyList_GetItem函数获取列表元素,它会返回指向列表元素的PyObject*,符合函数参数要求。同时注意PyArray_SETITEM的参数要求,需要将C的double转为Python的float对象。
修复后的循环代码:
for (i=0; i<element_count; ++i){ // 获取列表中的元素指针 PyObject* x_item = PyList_GetItem(x_int_list, i); int x = (int) PyLong_AsLong(x_item); PyObject* y_item = PyList_GetItem(y_int_list, i); int y = (int) PyLong_AsLong(y_item); PyObject* val_item = PyList_GetItem(val_float_list, i); double v = PyFloat_AsDouble(val_item); // 返回值本身就是double,无需强制转换 // 将C的double转为Python float对象,再设置到数组中 PyArray_SETITEM(out_array, PyArray_GETPTR2(out_array, y, x), PyFloat_FromDouble(v)); }
额外注意事项
PyList_GetItem返回的是借用引用,不需要手动调用Py_DECREF释放。- 建议添加错误检查:比如判断
PyList_GetItem是否返回NULL,以及PyLong_AsLong/PyFloat_AsDouble转换是否失败(可通过PyErr_Occurred()判断),避免程序崩溃。 PyArray_Zeros需要确保shape_int_tuple是合法的整数元组,否则会返回NULL,建议提前检查。
内容的提问来源于stack exchange,提问作者user1209675
相关产品推荐
相关产品推荐

