从ctypes数组获取数据到numpy
我正在使用一个用Python(通过ctypes
)封装的C库来进行一系列计算。在运行的不同阶段,我想把数据传回Python,特别是numpy
数组。
我使用的封装方式有两种不同的数组数据返回类型(这是我特别感兴趣的):
ctypes
数组:当我执行type(x)
时(这里的x是ctypes
数组),返回的是<class 'module_name.wrapper_class_name.c_double_Array_12000'>
。我知道这个数据是文档中提到的内部数据的一个副本,我可以很容易地把它转成numpy
数组:>>> np.ctypeslib.as_array(x)
这样可以得到一个一维的numpy
数组。
ctype
指向数据的指针:在这种情况下,根据库的文档,我了解到我得到了一个指向直接存储和使用的数据的指针。当我执行type(y)
时(这里的y是指针),返回的是<class 'module_name.wrapper_class_name.LP_c_double'>
。在这种情况下,我仍然可以像y[0][2]
这样索引数据,但我只能通过一种非常麻烦的方式把它转成numpy:>>> np.frombuffer(np.core.multiarray.int_asbuffer( ctypes.addressof(y.contents), array_length*np.dtype(float).itemsize))
我在一个旧的numpy
邮件列表中找到了这个方法,这是Travis Oliphant的一个帖子,但在numpy
文档中没有找到。如果我尝试上面的方法,结果是:
>>> np.ctypeslib.as_array(y)
...
... BUNCH OF STACK INFORMATION
...
AttributeError: 'LP_c_double' object has no attribute '__array_interface__'
请问这个np.frombuffer
的方法是最好的还是唯一的方法吗?我愿意接受其他建议,但我仍然希望使用numpy
,因为我有很多依赖于numpy
功能的后处理代码,想用这些数据来处理。
6 个回答
np.ctypeslib.as_array
就是你在这里需要的全部。
从一个数组:
c_arr = (c_float * 8)()
np.ctypeslib.as_array(c_arr)
从一个指针:
c_arr = (c_float * 8)()
ptr = ctypes.pointer(c_arr[0])
np.ctypeslib.as_array(ptr, shape=(8,))
另一种可能性(这可能需要比第一个答案写的时候更新的库版本——我用ctypes 1.1.0
和numpy 1.5.0b2
测试过类似的东西)是从指针转换到数组。
np.ctypeslib.as_array(
(ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))
这似乎仍然保持了共享所有权的特性,所以你可能需要确保最终释放底层的缓冲区。
从ctypes指针对象创建NumPy数组是个麻烦的事情。因为不清楚这个指针指向的内存到底是谁的。什么时候会释放这块内存?这块内存能用多久?尽量避免这种情况。如果能的话,直接在Python代码中创建数组,然后传给C函数,这样既简单又安全。使用一个不懂Python的C函数分配的内存,反而会失去高层语言在内存管理上的一些优势。
如果你真的确定有人在管理这块内存,你可以创建一个支持Python“缓冲协议”的对象,然后用这个缓冲对象创建NumPy数组。你在帖子中提到了一种创建缓冲对象的方法,就是用不公开的int_asbuffer()
函数:
buffer = numpy.core.multiarray.int_asbuffer(
ctypes.addressof(y.contents), 8*array_length)
(注意,我把8
替换成了np.dtype(float).itemsize
。在任何平台上,这个值总是8。)另一种创建缓冲对象的方法是通过ctypes调用Python C API中的PyBuffer_FromMemory()
函数:
buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory
buffer_from_memory.restype = ctypes.py_object
buffer = buffer_from_memory(y, 8*array_length)
用这两种方法,你都可以通过buffer
来创建NumPy数组:
a = numpy.frombuffer(buffer, float)
(我其实不太明白你为什么用.astype()
而不是给frombuffer
传第二个参数;而且,我也想知道你为什么用np.int
,之前你说数组里是double
类型。)
我怕这事情不会比这更简单了,但也没那么糟糕,对吧?你可以把所有复杂的细节放在一个包装函数里,这样就不用再担心了。