从ctypes数组获取数据到numpy

44 投票
6 回答
39550 浏览
提问于 2025-04-16 08:02

我正在使用一个用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 个回答

13

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,))
15

另一种可能性(这可能需要比第一个答案写的时候更新的库版本——我用ctypes 1.1.0numpy 1.5.0b2测试过类似的东西)是从指针转换到数组。

np.ctypeslib.as_array(
    (ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))

这似乎仍然保持了共享所有权的特性,所以你可能需要确保最终释放底层的缓冲区。

31

从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类型。)

我怕这事情不会比这更简单了,但也没那么糟糕,对吧?你可以把所有复杂的细节放在一个包装函数里,这样就不用再担心了。

撰写回答