我可以强迫一个numpy ndarray获取其内存的所有权吗?

时间:2012-01-03 06:36:06

标签: python c numpy free ctypes

我有一个C函数,mallocs()并填充一个浮点数的二维数组。它“返回”该地址和数组的大小。签名是

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()中的内存。返回副本而不是原始数组。这很烦人,因为它应该是一个不必要的内存副本。

  3. 有没有办法做我想要的?我不能修改C函数本身,虽然我可以在库中添加另一个C函数,如果这有用的话。

2 个答案:

答案 0 :(得分:6)

我只是偶然发现了这个问题,这在2013年8月仍然是一个问题.Numpy对OWNDATA标志非常挑剔:在Python级别上无法修改它,所以ctypes很可能不能这样做。在numpy C-API级别 - 现在我们讨论的是一种完全不同的制作Python扩展模块的方法 - 必须用以下方式明确设置标志:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

on numpy< 1.7,一个必须更明确:

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

如果对底层C函数/库有任何控制权,最好的解决方案是从Python传递一个适当大小的空numpy数组,以存储结果。基本原则是应始终完成内存分配在可能的最高级别,在这种情况下在Python解释器的级别上。


正如kynan在下面评论过的,如果您使用Cython,则必须手动公开函数PyArray_ENABLEFLAGS,请参阅此帖子Force NumPy ndarray to take ownership of its memory in Cython

相关文件为herehere

答案 1 :(得分: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 */

然后我会编写get_array_c的Python包装器[1]来分配数组,然后调用get_array_c_nomalloc。然后Python 拥有内存。您可以将此包装器集成到库中,这样您的用户就不必了解get_array_c_nomalloc的存在。

[1]这不再是一个包装器,而是一个适配器。