Получение данных из массива 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

Другие вопросы по тегам