Получение данных из массива ctypes в numpy
Я использую Python (через ctypes
) обернутая библиотека C для запуска серии вычислений. На разных этапах работы я хочу получить данные в Python, и, в частности, numpy
массивы.
Обертка, которую я использую, выполняет два разных типа возврата для данных массива (что представляет особый интерес для меня):
ctypes
Массив: когда я делаюtype(x)
(где х этоctypes
массив, я получаю<class 'module_name.wrapper_class_name.c_double_Array_12000'>
взамен Я знаю, что эти данные являются копией внутренних данных из документации, и я могу получить их вnumpy
массив легко:>>> np.ctypeslib.as_array(x)
Это возвращает 1D numpy
массив данных.
ctype
указатель на данные: в данном случае из документации библиотеки я понимаю, что получаю указатель на данные, хранящиеся и используемые непосредственно в библиотеке. Сыворотка я делаюtype(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
список рассылки от Трэвиса Олифанта, но не в numpy
документация. Если вместо этого подхода я попробую, как указано выше, я получу следующее:
>>> np.ctypeslib.as_array(y)
...
... BUNCH OF STACK INFORMATION
...
AttributeError: 'LP_c_double' object has no attribute '__array_interface__'
Это np.frombuffer
подойти лучше или единственный способ сделать это? Я открыт для других предложений, но все равно должен использовать numpy
как у меня есть много другого кода пост-обработки, который опирается на numpy
функциональность, которую я хочу использовать с этими данными.
5 ответов
Создание массивов NumPy из объекта указателя ctypes является проблематичной операцией. Неясно, кому на самом деле принадлежит память, на которую указывает указатель. Когда он снова будет освобожден? Как долго это действует? По возможности я старался избегать такого рода конструкции. Гораздо проще и безопаснее создавать массивы в коде 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)
Для обоих этих способов вы можете создать массив NumPy из buffer
от
a = numpy.frombuffer(buffer, float)
(Я на самом деле не понимаю, почему вы используете .astype()
вместо второго параметра frombuffer
; кроме того, мне интересно, почему вы используете np.int
в то время как вы сказали ранее, что массив содержит double
с.)
Боюсь, это не станет намного легче, чем это, но это не так уж плохо, не так ли? Вы можете похоронить все уродливые детали в функции-обертке и больше не беспокоиться об этом.
Другая возможность (которая может потребовать более новых версий библиотек, чем доступно, когда был написан первый ответ - я протестировал нечто подобное с ctypes 1.1.0
а также numpy 1.5.0b2
) для преобразования из указателя в массив.
np.ctypeslib.as_array(
(ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))
Похоже, что у него все еще есть семантика общего владения, поэтому вам, вероятно, необходимо в конце концов освободить основной буфер.
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 3. Как общее решение для преобразования указателя ctypes в numy 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
С помощью np.ndarrays
в качестве ctypes
аргументы
Предпочтительный подход - использование ndpointer
, как упоминалось в numpy-docs.
Этот подход более гибкий, чем использование, например, POINTER(c_double), поскольку можно указать несколько ограничений, которые проверяются при вызове функции ctypes. К ним относятся тип данных, количество измерений, форма и флаги. Если данный массив не удовлетворяет указанным ограничениям, возникает ошибка TypeError.
Минимальный воспроизводимый пример
Вызов memcpy из python. В конце концов, имя файла стандартной 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
аргументы ndpointer
не соответствовать размерам arr_from/arr_to
, код не работает с ArgumentError
.
Поскольку название этого вопроса довольно общее,...
Строительство np.ndarray
из ctypes.c_void_p
результат
Минимальный воспроизводимый пример
В следующем примере некоторая память выделяется malloc и заполняется нулями с помощью memset. Затем создается массив 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))
Выход
<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]
Если вы в порядке с созданием массивов в 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