我可以强制numpy ndarray拥有其内存吗?

14 投票
2 回答
3659 浏览
提问于 2025-04-17 09:25

我有一个C语言的函数,它会分配内存并填充一个二维的浮点数组。这个函数“返回”这个数组的地址和大小。函数的定义是这样的:

int get_array_c(float** addr, int* nrows, int* ncols);

我想从Python中调用这个函数,所以我使用了ctypes。

import ctypes
mylib = ctypes.cdll.LoadLibrary('mylib.so')
get_array_c = mylib.get_array_c

我一直没弄明白怎么用ctypes来指定参数类型。我通常会为每个用到的C函数写一个Python的包装函数,并确保在这个包装函数中把类型写对。这个浮点数组是按列优先的矩阵,我想把它转成numpy.ndarray。但这个数组比较大,所以我想直接使用C函数分配的内存,而不是复制一份。(我刚在这个StackOverflow的回答中发现了PyBuffer_FromMemory的内容:https://stackoverflow.com/a/4355701/3691

buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory
buffer_from_memory.restype = ctypes.py_object

import numpy
def get_array_py():
    nrows = ctypes.c_int()
    ncols = ctypes.c_int()
    addr_ptr = ctypes.POINTER(ctypes.c_float)()
    get_array_c(ctypes.byref(addr_ptr), ctypes.byref(nrows), ctypes.byref(ncols))
    buf = buffer_from_memory(addr_ptr, 4 * nrows * ncols)
    return numpy.ndarray((nrows, ncols), dtype=numpy.float32, order='F',
                         buffer=buf)

这样似乎能给我一个包含正确值的数组。但我很确定这会造成内存泄漏。

>>> a = get_array_py()
>>> a.flags.owndata
False

这个数组并不拥有那块内存。没错;默认情况下,当数组是从一个缓冲区创建时,它不应该拥有那块内存。但在这种情况下,它应该拥有。当这个numpy数组被删除时,我希望Python能帮我释放那块缓冲区的内存。看起来如果我能强制设置owndata为True,那就可以了,但owndata是不能设置的。

不太满意的解决方案:

  1. 让调用get_array_py()的地方负责释放内存。这太烦人了;调用者应该能把这个numpy数组当成其他任何numpy数组来处理。

  2. 在get_array_py中把原始数组复制到一个新的numpy数组(它有自己独立的内存),然后删除第一个数组,并在get_array_py()中释放内存。返回这个复制的数组,而不是原始数组。这很烦,因为这本来不应该需要额外的内存复制。

有没有办法实现我想要的?我不能修改C函数本身,不过如果有帮助的话,我可以往库里添加另一个C函数。

2 个回答

1

我通常会从我的C库中导出两个函数:

int get_array_c_nomalloc(float* addr, int nrows, int ncols); /* Pass addr as argument */
int get_array_c(float **addr, int nrows, int ncols); /* Calls function above */

接着,我会写一个Python的适配器,用来调用get_array_c这个函数来分配数组,然后再调用get_array_c_nomalloc。这样一来,Python就可以自己管理内存了。你可以把这个适配器集成到你的库里,这样用户就不需要知道get_array_c_nomalloc这个函数的存在。

顺便说一下,这个适配器其实不太算是一个包装器了,更像是一个适配器。

6

我刚看到这个问题,直到2013年8月仍然存在。Numpy对OWNDATA这个标志非常挑剔:在Python层面上没有办法修改它,所以ctypes很可能也无法做到这一点。在Numpy的C-API层面上——这就涉及到一种完全不同的制作Python扩展模块的方法——必须明确地设置这个标志,方法是:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

在Numpy版本小于1.7时,甚至需要更加明确:

((PyArrayObject*)arr)->flags |= NPY_OWNDATA;

如果你能控制底层的C函数或库,最好的解决办法是从Python传递一个合适大小的空Numpy数组来存储结果。基本原则是,内存分配应该尽可能在最高层次上进行,在这种情况下,就是在Python解释器的层面上。


正如kynan在下面评论的那样,如果你使用Cython,你需要手动暴露函数PyArray_ENABLEFLAGS,可以参考这篇帖子 强制NumPy ndarray在Cython中拥有其内存的所有权

相关文档可以在 这里这里 找到。

撰写回答