将数据从ctypes数组转换为numpy

dtl*_*ier 30 python ctypes numpy

我正在使用Python(via 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)
    
    Run Code Online (Sandbox Code Playgroud)

这将返回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))
    
    Run Code Online (Sandbox Code Playgroud)

我在Travis Oliphant的旧numpy邮件列表帖子中找到了这个,但在numpy文档中没有.如果不是这种方法,我尝试如上,我得到以下内容:

>>> np.ctypeslib.as_array(y)
...
...  BUNCH OF STACK INFORMATION
...
AttributeError: 'LP_c_double' object has no attribute '__array_interface__'
Run Code Online (Sandbox Code Playgroud)

这种np.frombuffer方法是最佳还是唯一的方法?我对其他建议numpy持开放态度,但我仍然希望使用,因为我有很多其他后处理代码,它依赖于numpy我想要对这些数据使用的功能.

Sve*_*ach 26

从ctypes指针对象创建NumPy数组是一个有问题的操作.目前还不清楚谁拥有指针所指向的内存.什么时候会再次被释放?有效期有多长?只要有可能,我会尽量避免这种结构.在Python代码中创建数组并将它们传递给C函数比使用不知道Python的C函数分配的内存要容易和安全得多.通过执行后者,您在某种程度上否定了使用高级语言来处理内存管理的优势.

如果你确定有人负责处理内存,你可以创建一个公开Python"缓冲协议"的对象,然后使用这个缓冲区对象创建一个NumPy数组.您通过未记录的int_asbuffer()函数提供了一种在帖子中创建缓冲区对象的方法:

buffer = numpy.core.multiarray.int_asbuffer(
    ctypes.addressof(y.contents), 8*array_length)
Run Code Online (Sandbox Code Playgroud)

(注意我替换8np.dtype(float).itemsize.在任何平台上它都是8.)创建缓冲区对象的另一种方法是PyBuffer_FromMemory()通过ctypes从Python C API 调用该函数:

buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory
buffer_from_memory.restype = ctypes.py_object
buffer = buffer_from_memory(y, 8*array_length)
Run Code Online (Sandbox Code Playgroud)

对于这两种方式,你可以从一个NumPy的阵列buffer

a = numpy.frombuffer(buffer, float)
Run Code Online (Sandbox Code Playgroud)

(我实际上不明白为什么你使用.astype()而不是第二个参数frombuffer;而且,我想知道你为什么使用np.int,而你之前说过,数组包含doubles.)

我担心它不会比这更容易,但它不是那么糟糕,你不觉得吗?你可以将所有丑陋的细节埋在包装函数中,不再担心它.

  • 对于python3,您可以使用`PyMemoryView_FromMemory`,而不是`PyBuffer_FromMemory`。许多以前称为缓冲区的东西现在称为内存视图。 (2认同)

see*_*ker 10

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

np.ctypeslib.as_array(
    (ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))
Run Code Online (Sandbox Code Playgroud)

这似乎仍然具有共享所有权语义,因此您可能需要确保最终释放底层缓冲区.

  • 或者没有来自numpy的特殊支持:你可以将`y`指针转换为指向数组类型的指针:`ap = ctypes.cast(y,ctypes.POINTER(ArrayType))`其中`ArrayType = ctypes.c_double*array_length`和从中创建numpy数组:`a = np.frombuffer(ap.contents)`.请参阅[如何将指向c数组的指针转换为python数组](http://stackoverflow.com/questions/7543675/how-to-convert-pointer-to-c-array-to-python-array) (2认同)

wor*_*rdy 10

这些都不适用于Python 3.作为在python 2和3中将ctypes指针转换为numpy ndarray的一般解决方案,我发现这有效(通过获取只读缓冲区):

def make_nd_array(c_pointer, shape, dtype=np.float64, order='C', own_data=True):
    arr_size = np.prod(shape[:]) * np.dtype(dtype).itemsize 
    if sys.version_info.major >= 3:
        buf_from_mem = ctypes.pythonapi.PyMemoryView_FromMemory
        buf_from_mem.restype = ctypes.py_object
        buf_from_mem.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_int)
        buffer = buf_from_mem(c_pointer, arr_size, 0x100)
    else:
        buf_from_mem = ctypes.pythonapi.PyBuffer_FromMemory
        buf_from_mem.restype = ctypes.py_object
        buffer = buf_from_mem(c_pointer, arr_size)
    arr = np.ndarray(tuple(shape[:]), dtype, buffer, order=order)
    if own_data and not arr.flags.owndata:
        return arr.copy()
    else:
        return arr
Run Code Online (Sandbox Code Playgroud)


Eri*_*ric 8

np.ctypeslib.as_array 这就是您所需要的一切。

从数组:

 c_arr = (c_float * 8)()
 np.ctypeslib.as_array(c_arr)
Run Code Online (Sandbox Code Playgroud)

从指针

 c_arr = (c_float * 8)()
 ptr = ctypes.pointer(c_arr[0])
 np.ctypeslib.as_array(ptr, shape=(8,))
Run Code Online (Sandbox Code Playgroud)


Mar*_*hke 5

用作np.ndarrays参数ctypes

更好的方法是使用ndpointer,如numpy-docs中所述。

这种方法比使用 POINTER(c_double) 等方法更灵活,因为可以指定多个限制,这些限制在调用 ctypes 函数时进行验证。其中包括数据类型、维数、形状和标志。如果给定的数组不满足指定的限制,则会引发 TypeError。

最小的、可重复的示例

从 python调用memcpylibc.so.6最终需要调整标准 C 库的文件名。

import ctypes
import numpy as np

n_bytes_f64 = 8
nrows = 2
ncols = 5

clib = ctypes.cdll.LoadLibrary("libc.so.6")

clib.memcpy.argtypes = [
    np.ctypeslib.ndpointer(dtype=np.float64, ndim=2, flags='C_CONTIGUOUS'),
    np.ctypeslib.ndpointer(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS'),
    ctypes.c_size_t]
clib.memcpy.restype = ctypes.c_void_p

arr_from = np.arange(nrows * ncols).astype(np.float64)
arr_to = np.empty(shape=(nrows, ncols), dtype=np.float64)

print('arr_from:', arr_from)
print('arr_to:', arr_to)

print('\ncalling clib.memcpy ...\n')
clib.memcpy(arr_to, arr_from, nrows * ncols * n_bytes_f64)

print('arr_from:', arr_from)
print('arr_to:', arr_to)
Run Code Online (Sandbox Code Playgroud)

输出

arr_from: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
arr_to: [[0.0e+000 4.9e-324 9.9e-324 1.5e-323 2.0e-323]
 [2.5e-323 3.0e-323 3.5e-323 4.0e-323 4.4e-323]]

calling clib.memcpy ...

arr_from: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
arr_to: [[0. 1. 2. 3. 4.]
 [5. 6. 7. 8. 9.]]
Run Code Online (Sandbox Code Playgroud)

如果将 的ndim=1/2参数修改ndpointer为与 的维度不一致arr_from/arr_to,则代码将失败并显示ArgumentError

由于这个问题的标题很笼统,...

np.ndarrayctypes.c_void_p结果构建

最小的、可重复的示例

在下面的示例中,一些内存由malloc分配并由memset填充 0 。然后构造一个 numpy 数组来访问该内存。当然,会发生一些所有权问题,因为 python 不会释放在 c 中分配的内存。为了避免内存泄漏,必须通过ctypes再次释放分配的内存。可以采用复制np.ndarray方法来获取所有权

import ctypes
import numpy as np

n_bytes_int = 4
size = 7

clib = ctypes.cdll.LoadLibrary("libc.so.6")

clib.malloc.argtypes = [ctypes.c_size_t]
clib.malloc.restype = ctypes.c_void_p

clib.memset.argtypes = [
    ctypes.c_void_p,
    ctypes.c_int,
    ctypes.c_size_t]
clib.memset.restype = np.ctypeslib.ndpointer(
    dtype=np.int32, ndim=1, flags='C_CONTIGUOUS')

clib.free.argtypes = [ctypes.c_void_p]
clib.free.restype = ctypes.c_void_p


pntr = clib.malloc(size * n_bytes_int)
ndpntr = clib.memset(pntr, 0, size * n_bytes_int)
print(type(ndpntr))
ctypes_pntr = ctypes.cast(ndpntr, ctypes.POINTER(ctypes.c_int))
print(type(ctypes_pntr))
print()
arr_noowner = np.ctypeslib.as_array(ctypes_pntr, shape=(size,))
arr_owner = np.ctypeslib.as_array(ctypes_pntr, shape=(size,)).copy()
# arr_owner = arr_noowner.copy()


print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

print('\nfree allocated memory again ...\n')
_ = clib.free(pntr)

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

print('\njust for fun: free some python-memory ...\n')
_ = clib.free(arr_owner.ctypes.data_as(ctypes.c_void_p))

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))
Run Code Online (Sandbox Code Playgroud)

输出

<class 'numpy.ctypeslib.ndpointer_<i4_1d_C_CONTIGUOUS'>
<class '__main__.LP_c_int'>

arr_noowner (at 104719884831376): [0 0 0 0 0 0 0]
arr_owner (at 104719884827744): [0 0 0 0 0 0 0]

free allocated memory again ...

arr_noowner (at 104719884831376): [ -7687536     24381 -28516336     24381         0         0         0]
arr_owner (at 104719884827744): [0 0 0 0 0 0 0]

just for fun: free some python-memory ...

arr_noowner (at 104719884831376): [ -7687536     24381 -28516336     24381         0         0         0]
arr_owner (at 104719884827744): [ -7779696     24381 -28516336     24381         0         0         0]
Run Code Online (Sandbox Code Playgroud)