Получение данных из массива 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)
Это возвращает массив данных 1D numpy
.
-
ctype
указатель на данные. В этом случае из документации библиотеки я понимаю, что получаю указатель на данные, хранящиеся и используемые непосредственно в библиотеке. Whey я do 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
, которую я хочу использовать с этими данными.
Ответы
Ответ 1
Создание массивов NumPy из объекта указателя ctypes является проблематичной операцией. Неясно, кто на самом деле владеет памятью, на которую указывает указатель. Когда он будет освобожден снова? Как долго это действует? По возможности я старался избежать такой конструкции. Гораздо проще и безопаснее создавать массивы в коде Python и передавать их функции C, чем использовать память, выделенную функцией Python-unaware 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()
из API Python C через ctypes:
buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory
buffer_from_memory.restype = ctypes.py_object
buffer = buffer_from_memory(y, 8*array_length)
Для обоих способов вы можете создать массив NumPy из buffer
на
a = numpy.frombuffer(buffer, float)
(на самом деле я не понимаю, почему вы используете .astype()
вместо второго параметра frombuffer
; кроме того, мне интересно, почему вы используете np.int
, а вы сказали ранее, что массив содержит double
s.)
Я боюсь, что это не будет намного легче, чем это, но это не так уж плохо, не так ли? Вы можете похоронить все уродливые детали в функции обертки и больше не беспокоиться об этом.
Ответ 2
Другая возможность (которая может потребовать более поздних версий библиотек, чем доступно, когда был написан первый ответ - я протестировал что-то подобное с ctypes 1.1.0
и numpy 1.5.0b2
) для преобразования из указателя в массив.
np.ctypeslib.as_array(
(ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))
Кажется, что у вас все еще есть семантика совместного использования, поэтому вам, вероятно, необходимо убедиться, что вы освободите базовый буфер в конце концов.
Ответ 3
Ни один из них не работал у меня на Python 3. Как общее решение для преобразования указателя ctypes в numpy ndarray в python 2 и 3, я нашел, что это сработало (через получение только для чтения):
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
Если вы в порядке с созданием массивов в 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