PyArray_SimpleNewFromData

4

我正在尝试编写一个C函数,它接受一个numpy数组对象,提取数据,进行一些操作,并返回另一个c数组作为numpy数组对象。所有工作都无缝进行,我使用python包装器帮助在python端轻松操作。然而,我面临着内存泄漏的问题。我有一个指向浮点数的输出指针,我malloc了它,并在返回调用python函数之前将其包装成Python数组对象。

PyObject *arr;
int nd = 2;
npy_intp dims[] = {5, 10};
double *data = some_function_that_returns_a_double_star(x, y, z);

arr = PyArray_SimpleNewFromData(nd, dims, NPY_DOUBLE, (void *)data);
return arr;

然而,这样做会导致内存泄漏,因为数据从未被释放。我查阅了一些资料,发现这是这类应用程序中的一个问题,解决方法也并非易事。我发现最有帮助的资源是这里。但我无法像该页面所说的那样实现析构函数。有人能帮助我吗?更具体地说,我正在寻找像这样的解决方案:

PyObject *arr;
int nd = 2;
npy_intp dims[] = {5, 10};
double *data = some_function_that_returns_a_double_star(x, y, z);

arr = PyArray_SimpleNewFromData(nd, dims, NPY_DOUBLE, (void *)data);
some_destructor_that_plug_memLeak_due_to_data_star(args);
return arr;
2个回答

7
链接中描述的技术是一个不错的方法:创建一个Python对象,在其销毁时知道如何释放内存,并将其作为返回数组的基础。
听起来你可能被创建新扩展类型的复杂性所压倒。幸运的是,这并不是必需的。Python带有一种类型,专门用于在销毁时执行任意C级清理:capsules,它们捆绑了指针和析构函数,并在销毁胶囊时调用析构函数。
要为您的内存创建一个胶囊,首先,我们定义一个析构函数:
void capsule_cleanup(PyObject *capsule) {
    void *memory = PyCapsule_GetPointer(capsule, NULL);
    // I'm going to assume your memory needs to be freed with free().
    // If it needs different cleanup, perform whatever that cleanup is
    // instead of calling free().
    free(memory);
}

并且你使用以下代码将胶囊设置为数组的基础:

PyObject *capsule = PyCapsule_New(data, NULL, capsule_cleanup);
PyArray_SetBaseObject((PyArrayObject *) arr, capsule);
// Do not Py_DECREF the capsule; PyArray_SetBaseObject stole your
// reference.

这样做可以确保您的内存在不再使用时被释放。

非常感谢...真的很棒...似乎已经解决了我的问题...我在寻找解决方法时无法找到此处。非常优雅。不过,只有几个指针,(a) void *memory = PyCapsule_GetPointer(capsule, NULL); (b) 当我编译上面的代码时,我得到以下警告 -passing argument 1 of ‘(int (*)(PyArrayObject *, PyObject *))*(PyArray_API + 2256u)’ from incompatible pointer type [-Wincompatible-pointer-types]有什么避免它的想法吗? - senior_mle
@ArnabSanyal:感谢指出缺少的参数;我忘记了胶囊名称是用于那个的。至于指针类型警告,缺少了强制转换。 - user2357112
不客气,@user2357112。这真的是一个救命稻草。测试过了,完美运行!! - senior_mle
我在caffe库中找到了这个,也许会有人帮忙解决:“boost python期望一个void(缺失)返回值,而import_array返回NULL用于python3。import_array1()强制使用void返回值。” - Roger Figueroa Quintero

7

虽然使用 PyCapsule 方法可以更加通用,但是你也可以通过设置 OWNDATA 标记让 numpy 在垃圾回收时帮你释放数组中的内存。

double *data = some_function_that_returns_a_double_star(x, y, z);
PyObject *arr = PyArray_SimpleNewFromData(nd, dims, NPY_DOUBLE, (void *)data);
PyArray_ENABLEFLAGS((PyArrayObject*) arr, NPY_ARRAY_OWNDATA);

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