2012-01-03 134 views
11

我有一個C函數mallocs()並填充浮點數的二維數組。它「返回」該地址和數組的大小。簽名是我可以強制讓一個numpy ndarray擁有它的內存嗎?

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函數分配的內存,而不是複製它。 (我剛剛發現這個PyBuffer_FromMemory東西,在這個StackOverflow的答案: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函數,如果這有幫助的話。

+0

這聽起來像一個痛苦的世界..我認爲你是要[segfault hell](http://xkcd.com/371/) – wim 2012-01-03 07:27:17

+0

我試過這個以及沒有成功使用ctypes。完整的擴展模塊使這成爲可能,但他們更多的工作來寫。 – 2012-02-01 20:20:25

回答

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的[1]〜分配陣列,然後調用get_array_c_nomalloc。然後Python 確實擁有內存。你可以將這個包裝器集成到你的庫中,這樣你的用戶永遠不必知道get_array_c_nomalloc的存在。

[1]這不是一個真正的包裝,而是一個適配器。

+0

對不起,我有get_array_c()錯誤的簽名!它需要int _pointers_用於nrows和ncols - 我不知道數組有多大,所以我不能在python中預分配數組。 – 2012-01-03 08:44:09

+0

好吧,你也可以讓你的python包裝器使用一個對象來保存引用/訪問內存,並使用終結器來釋放數組......不知道這是否違反你的審美或不,但用戶贏得'不得不明確地釋放內存。 – Matthew 2012-01-03 15:32:03

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,看到這個帖子Force NumPy ndarray to take ownership of its memory in Cython

相關文件爲herehere

+0

我如何在Cython中實現同樣的效果?不幸的是,'PyArray_ENABLEFLAGS'似乎不會暴露在'numpy.pxd'中。 – kynan 2014-05-07 13:18:41

+1

如果所需的功能未暴露給Cython,您可以修補Cython或編輯它手動生成的C文件。 – Stefan 2014-05-07 13:26:19

+0

這些對我來說都不是非常可持續的選擇。我嘗試在'pyx文件中擴展'numpy.pxd'暴露的東西[但沒有運氣](https://gist.github.com/kynan/ade36155b497c87e0bc5)。 – kynan 2014-05-07 16:44:16