【问题标题】:Getting data from ctypes array into numpy从 ctypes 数组中获取数据到 numpy
【发布时间】:2011-05-20 07:46:20
【问题描述】:

我正在使用 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 邮件列表thread from 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 功能的后处理代码,我想与这些数据一起使用

【问题讨论】:

  • 你能控制 C 库吗?你能改变图书馆的API吗?
  • 是的 - 我有来源。我不确定要走哪条路,因为指针方法允许 Python 直接作用于数据,我认为在某些情况下这可能是一个优势。不过,就我而言,是的,将所有内容都以ctype 数组的形式出现将是一个优势。有什么建议吗?
  • 我建议让库使用您在 Python 中分配的 (NumPy-) 数组并传递给库。这样,您可以对相同的记忆采取行动,但您不必费心进行任何尴尬的转换。您已经有一个 NumPy 数组,并且通过使用 numpy.ctypeslib.ndpointer 作为函数的 ctypes 包装器的参数类型,可以很好地支持将其传递给库。 (如果不清楚,请问...)

标签: python numpy ctypes


【解决方案1】:

从 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,而你之前说数组包含doubles。)

恐怕不会比这更容易了,但也没有那么糟糕,你不觉得吗?您可以将所有丑陋的细节都隐藏在包装函数中,而不必再担心了。

【讨论】:

  • 太好了 - 感谢您对优缺点的概述。 .astype() 电话只是一个意外的副本和过去的错误。我现在已经把它从我的问题中拉出来了。感谢您了解这一点。
  • 对于python3,你可以使用PyMemoryView_FromMemory,而不是PyBuffer_FromMemory。许多以前称为缓冲区的东西现在称为内存视图。
  • numpy.core.multiarray.int_asbuffer 从未在 python 3 上工作过,不再存在
【解决方案2】:

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

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

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

【讨论】:

  • 或者没有 numpy 的特殊支持:您可以将 y 指针转换为指向数组类型的指针:ap = ctypes.cast(y, ctypes.POINTER(ArrayType)) 其中ArrayType = ctypes.c_double * array_length 并从中创建 numpy 数组:a = np.frombuffer(ap.contents)。见How to convert pointer to c array to python array
  • 我正在尝试这个,但是 ap 对象没有成员,内容。
  • @TotteKarlsson:链接中的代码按原样工作(我已经测试过了)。这可能是您代码中的错误(也可能是各种 Python 版本之间的差异,但可能性较小)。如果你还没有修复它; create a minimal but complete code example,指定你的操作系统,python 版本和post it as a new SO question
【解决方案3】:

这些在 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

【讨论】:

  • 这太棒了!数组旋转出来 - 奇怪:)
【解决方案4】:

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

【讨论】:

  • 这对我来说非常有效,谢谢。我将带有数组指针成员的python ctypes.Structure 传递给对数据进行操作的C 函数;然后在python中读回成员数组的内容。 python 3.8,numpy 1.19,ctypes 1.1.0
【解决方案5】:

使用np.ndarrays 作为ctypes 参数

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

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

最小的、可重现的示例

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

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)

输出

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.]]

如果将ndpointerndim=1/2 参数修改为与arr_from/arr_to 的尺寸不一致,则代码将失败并返回ArgumentError

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

ctypes.c_void_p 结果构造np.ndarray

最小的、可重现的示例

在下面的例子中,malloc 分配了一些内存,memset 用 0 填充。然后构造一个 numpy 数组,以访问该内存。当然会发生一些所有权问题,因为 python 不会释放在 c 中分配的内存。为了避免内存泄漏,必须通过 ctypes 再次free 分配的内存。 copy 方法可用于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))

输出

<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]

【讨论】:

    【解决方案6】:

    如果您可以在 python 中创建数组,下面的二维数组示例适用于 python3:

    import numpy as np
    import ctypes
    
    OutType = (ctypes.c_float * 4) * 6
    out = OutType()
    YourCfunction = ctypes.CDLL('./yourlib.so').voidreturningfunctionwithweirdname
    YourCfunction.argtypes = [ctypes.POINTER(ctypes.c_float)]*3, ctypes.POINTER(ctypes.c_float)]*5, OutType]
    YourCfunction(input1, input2, out)
    out = np.array(out) # convert it to numpy
    
    print(out)
    

    numpy 和 ctypes 版本是 1.11.1 和 1.1.0

    【讨论】:

      猜你喜欢
      • 2012-04-13
      • 2015-07-13
      • 2018-07-29
      • 2017-05-12
      • 2014-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-21
      相关资源
      最近更新 更多