我正在使用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)
这将返回数据的1D 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
功能
答案 0 :(得分:25)
从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。)创建缓冲区对象的另一种方法是从中调用PyBuffer_FromMemory()
函数Python C API通过ctypes:
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
峰)
我担心它不会比这更容易,但它不是那么糟糕,你不觉得吗?你可以将所有丑陋的细节埋在包装函数中,不再担心它。
答案 1 :(得分:9)
另一种可能性(可能需要比编写第一个答案时可用的更新版本的库 - 我测试了与ctypes 1.1.0
和numpy 1.5.0b2
类似的东西)是从指针转换为阵列。
np.ctypeslib.as_array(
(ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))
这似乎仍然具有共享所有权语义,因此您可能需要确保最终释放底层缓冲区。
答案 2 :(得分:8)
在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
答案 3 :(得分:2)
np.ndarrays
作为ctypes
参数更可取的方法是使用numpy-docs中提到的ndpointer
。
这种方法比使用例如 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.]]
如果将ndim=1/2
的{{1}}参数修改为与ndpointer
的尺寸不一致,则代码将失败,并显示arr_from/arr_to
。
这个问题的标题很笼统,...
ArgumentError
结果构建np.ndarray
最小的可复制示例
在以下示例中,某些内存由malloc分配,并由memset填充0。然后构造一个numpy数组,以访问该内存。当然会发生一些所有权问题,因为python不会释放在c中分配的内存。为了避免内存泄漏,必须再次free通过ctypes分配内存。 ctypes.c_void_p
可以使用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))
答案 4 :(得分:0)
如果您可以在python中创建数组,以下带有2d数组的示例可以在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
答案 5 :(得分:0)
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,))