Spec-Zone.ru › NumPy 1.21

numpy.i: файл интерфейса SWIG для NumPy

Введение

Инструмент Simple Wrapper and Interface Generator (или SWIG) — мощный инструмент для генерации обёртки кода для взаимодействия с широким спектром языков сценариев. SWIG может анализировать заголовочные файлы и, используя только прототипы кода, создавать интерфейс для целевого языка. Однако SWIG не всемогущ. Например, он не может определить из прототипа:

double rms(double* seq, int n);

что именно seq представляет собой. Является ли это одиночным значением, которое нужно изменить на месте? Является ли это массивом, и если да, то какова его длина? Является ли это значение только входным? Только выходным? Входным-выходным? SWIG не может определить эти детали и не пытается это сделать.

Если бы мы разработали rms, мы, вероятно, сделали бы его процедурой, которая принимает массив только для входных данных длиной n значений double, называемый seq, и возвращает среднеквадратичное отклонение. Однако стандартное поведение SWIG будет заключаться в создании обёртки функции, которая компилируется, но практически не может использоваться из языка сценариев так, как задумывалась процедура на C.

Для Python предпочтительный способ обработки смежных (или, технически, строковыми) блоков однородных данных — с помощью NumPy, который предоставляет полный объектно-ориентированный доступ к многомерным массивам данных. Поэтому наиболее логичным интерфейсом Python для функции rms будет (включая строку документации):

def rms(seq):
    """
    rms: return the root mean square of a sequence
    rms(numpy.ndarray) -> double
    rms(list) -> double
    rms(tuple) -> double
    """

где seq будет массивом NumPy значений double, а его длина n будет извлечена из seq внутри перед передачей в процедуру на C. Ещё лучше, поскольку NumPy поддерживает построение массивов из произвольных последовательностей Python, seq само по себе может быть практически произвольной последовательностью (поскольку каждый элемент может быть преобразован в double) и код обёртки преобразует его во внутренний массив NumPy перед извлечением данных и длины.

SWIG позволяет определять эти типы преобразований с помощью механизма, называемого typemaps. Этот документ содержит информацию о том, как использовать numpy.i, файл интерфейса SWIG, который определяет серию typemaps, предназначенных для упрощения реализации вышеописанных преобразований, связанных с массивами. Например, предположим, что прототип функции rms , определённой выше, находится в заголовочном файле под названием rms.h. Для получения интерфейса Python, обсуждаемого выше, ваш файл интерфейса SWIG должен содержать следующее:

%{
#define SWIG_FILE_WITH_INIT
#include "rms.h"
%}

%include "numpy.i"

%init %{
import_array();
%}

%apply (double* IN_ARRAY1, int DIM1) {(double* seq, int n)};
%include "rms.h"

Typemaps основаны на списке одного или нескольких аргументов функции, либо по типу, либо по типу и имени. Мы будем называть такие списки подписями. Одна из многих typemaps, определённых в numpy.i, используется выше и имеет подпись (double* IN_ARRAY1, int DIM1). Имена аргументов предполагают, что аргумент double* является одномерным массивом входных данных, а int представляет размер этого измерения. Это именно тот шаблон, который используется в прототипе rms.

Вероятнее всего, у прототипов, которые нужно обернуть, не будут аргументы с именами IN_ARRAY1 и DIM1. Мы используем директиву SWIG %apply для применения typemap для одномерных входных массивов типа double к фактическому прототипу, используемому функцией rms. Эффективное использование numpy.i требует знания доступных typemaps и их предназначения.

Файл интерфейса SWIG, который включает вышеупомянутые директивы SWIG, сгенерирует код обёртки, похожий на:

 1 PyObject *_wrap_rms(PyObject *args) {
 2   PyObject *resultobj = 0;
 3   double *arg1 = (double *) 0 ;
 4   int arg2 ;
 5   double result;
 6   PyArrayObject *array1 = NULL ;
 7   int is_new_object1 = 0 ;
 8   PyObject * obj0 = 0 ;
 9
10   if (!PyArg_ParseTuple(args,(char *)"O:rms",&obj0)) SWIG_fail;
11   {
12     array1 = obj_to_array_contiguous_allow_conversion(
13                  obj0, NPY_DOUBLE, &is_new_object1);
14     npy_intp size[1] = {
15       -1
16     };
17     if (!array1 || !require_dimensions(array1, 1) ||
18         !require_size(array1, size, 1)) SWIG_fail;
19     arg1 = (double*) array1->data;
20     arg2 = (int) array1->dimensions[0];
21   }
22   result = (double)rms(arg1,arg2);
23   resultobj = SWIG_From_double((double)(result));
24   {
25     if (is_new_object1 && array1) Py_DECREF(array1);
26   }
27   return resultobj;
28 fail:
29   {
30     if (is_new_object1 && array1) Py_DECREF(array1);
31   }
32   return NULL;
33 }

Typemaps из numpy.i отвечают за следующие строки кода: 12–20, 25 и 30. Строка 10 анализирует входные данные функции rms. Из формата строки "O:rms" видно, что список аргументов должен быть единственным объектом Python (указанным O перед двоеточием), указатель которого хранится в obj0. Несколько функций, предоставленных numpy.i, вызываются для создания и проверки (возможного) преобразования из обычного объекта Python в массив NumPy. Эти функции объясняются в разделе Вспомогательные функции, но, надеюсь, их названия понятны. В строке 12 мы используем obj0 для создания массива NumPy. В строке 17 мы проверяем корректность результата: он не нулевой и имеет одномерность произвольной длины. После проверки этих состояний, мы извлекаем буфер данных и длину в строках 19 и 20, чтобы мы могли вызвать базовую функцию C в строке 22. Строка 25 выполняет управление памятью в случае, если мы создали новый массив, который больше не нужен.

В этом коде имеется значительное количество обработки ошибок. Обратите внимание, что SWIG_fail — это макрос для goto fail, который ссылается на метку в строке 28. Если пользователь предоставляет неправильное количество аргументов, это будет обнаружено в строке 10. Если построение массива NumPy завершается неудачно или создаёт массив с неправильным числом измерений, эти ошибки будут обнаружены в строке 17. И, наконец, если будет обнаружена ошибка, память всё равно будет обработана правильно в строке 30.

Обратите внимание, что если подпись функции C имела другую последовательность:

double rms(int n, double* seq);

то SWIG не сопоставил бы подпись typemap, указанную выше, со списком аргументов для rms. К счастью, у numpy.i есть набор typemaps с указателем данных, указанным последним:

%apply (int DIM1, double* IN_ARRAY1) {(int n, double* seq)};

Это просто переключает определения arg1 и arg2 в строках 3 и 4 сгенерированного кода выше и их присваивания в строках 19 и 20.

Использование numpy.i

Файл numpy.i в настоящее время находится в подкаталоге tools/swig в каталоге установки numpy. Обычно вы захотите скопировать его в каталог, где разрабатываете свои обёртки.

Простой модуль, использующий только один файл интерфейса SWIG, должен включать следующее:

%{
#define SWIG_FILE_WITH_INIT
%}
%include "numpy.i"
%init %{
import_array();
%}

В скомпилированном модуле Python import_array() должен вызываться только один раз. Это может происходить в файле C/C++, который вы написали и который связан с модулем. Если это так, то ни один из ваших файлов интерфейса не должен #define SWIG_FILE_WITH_INIT или вызывать import_array(). Или этот вызов инициализации может быть в файле обёртки, сгенерированном SWIG из файла интерфейса, который содержит блок %init как указано выше. Если это так, и у вас есть более одного файла интерфейса SWIG, то только один файл интерфейса должен #define SWIG_FILE_WITH_INIT и вызывать import_array().

Доступные typemaps

Директивы typemap, предоставляемые numpy.i для массивов различных типов данных, скажем, double и int, и измерений различных типов, скажем, int или long, идентичны друг другу за исключением спецификаций типов C и NumPy. Поэтому typemaps реализуются (обычно за кулисами) с помощью макроса:

%numpy_typemaps(DATA_TYPE, DATA_TYPECODE, DIM_TYPE)

который может быть вызван для соответствующих (DATA_TYPE, DATA_TYPECODE, DIM_TYPE) троек. Например:

%numpy_typemaps(double, NPY_DOUBLE, int)
%numpy_typemaps(int,    NPY_INT   , int)

Файл интерфейса numpy.i использует макрос %numpy_typemaps для реализации typemaps для следующих типов данных C и int типов измерений:

  • signed char
  • unsigned char
  • short
  • unsigned short
  • int
  • unsigned int
  • long
  • unsigned long
  • long long
  • unsigned long long
  • float
  • double

В следующих описаниях мы будем ссылаться на общий тип DATA_TYPE, который может быть любым из перечисленных выше типов данных C, и DIM_TYPE, который должен быть одним из многих типов целых чисел.

Подписи typemap в основном отличаются по имени указателя буфера. Имена с FARRAY предназначены для массивов с порядком Fortran, а имена с ARRAY — для массивов с порядком C (или 1D массивов).

Входные массивы

Входные массивы определяются как массивы данных, которые передаются в процедуру, но не изменяются на месте и не возвращаются пользователю. Массив Python на входе, следовательно, может быть почти любой последовательностью Python (например, списком), который может быть преобразован в требуемый тип массива. Подписи входных массивов:

1D:

  • ( DATA_TYPE IN_ARRAY1[ANY] )
  • ( DATA_TYPE* IN_ARRAY1, int DIM1 )
  • ( int DIM1, DATA_TYPE* IN_ARRAY1 )

2D:

  • ( DATA_TYPE IN_ARRAY2[ANY][ANY] )
  • ( DATA_TYPE* IN_ARRAY2, int DIM1, int DIM2 )
  • ( int DIM1, int DIM2, DATA_TYPE* IN_ARRAY2 )
  • ( DATA_TYPE* IN_FARRAY2, int DIM1, int DIM2 )
  • ( int DIM1, int DIM2, DATA_TYPE* IN_FARRAY2 )

3D:

  • ( DATA_TYPE IN_ARRAY3[ANY][ANY][ANY] )
  • ( DATA_TYPE* IN_ARRAY3, int DIM1, int DIM2, int DIM3 )
  • ( int DIM1, int DIM2, int DIM3, DATA_TYPE* IN_ARRAY3 )
  • ( DATA_TYPE* IN_FARRAY3, int DIM1, int DIM2, int DIM3 )
  • ( int DIM1, int DIM2, int DIM3, DATA_TYPE* IN_FARRAY3 )

4D:

  • (DATA_TYPE IN_ARRAY4[ANY][ANY][ANY][ANY])
  • (DATA_TYPE* IN_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
  • (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, , DIM_TYPE DIM4, DATA_TYPE* IN_ARRAY4)
  • (DATA_TYPE* IN_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
  • (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* IN_FARRAY4)

Первая указанная подпись, ( DATA_TYPE IN_ARRAY[ANY] ), предназначена для одномерных массивов с жёстко заданными измерениями. Аналогично, ( DATA_TYPE IN_ARRAY2[ANY][ANY] ) предназначена для двумерных массивов с жёстко заданными измерениями, и так далее для трёхмерных.

Массивы на месте

Массивы на месте определяются как массивы, которые изменяются на месте. Значения входных данных могут или не могут использоваться, но значения в момент возвращения функции являются существенными. Предоставленный аргумент Python, следовательно, должен быть массивом NumPy требуемого типа. Подписи массивов на месте:

1D:

  • ( DATA_TYPE INPLACE_ARRAY1[ANY] )
  • ( DATA_TYPE* INPLACE_ARRAY1, int DIM1 )
  • ( int DIM1, DATA_TYPE* INPLACE_ARRAY1 )

2D:

  • ( DATA_TYPE INPLACE_ARRAY2[ANY][ANY] )
  • ( DATA_TYPE* INPLACE_ARRAY2, int DIM1, int DIM2 )
  • ( int DIM1, int DIM2, DATA_TYPE* INPLACE_ARRAY2 )
  • ( DATA_TYPE* INPLACE_FARRAY2, int DIM1, int DIM2 )
  • ( int DIM1, int DIM2, DATA_TYPE* INPLACE_FARRAY2 )

3D:

  • ( DATA_TYPE INPLACE_ARRAY3[ANY][ANY][ANY] )
  • ( DATA_TYPE* INPLACE_ARRAY3, int DIM1, int DIM2, int DIM3 )
  • ( int DIM1, int DIM2, int DIM3, DATA_TYPE* INPLACE_ARRAY3 )
  • ( DATA_TYPE* INPLACE_FARRAY3, int DIM1, int DIM2, int DIM3 )
  • ( int DIM1, int DIM2, int DIM3, DATA_TYPE* INPLACE_FARRAY3 )

4D:

  • (DATA_TYPE INPLACE_ARRAY4[ANY][ANY][ANY][ANY])
  • (DATA_TYPE* INPLACE_ARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
  • (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, , DIM_TYPE DIM4, DATA_TYPE* INPLACE_ARRAY4)
  • (DATA_TYPE* INPLACE_FARRAY4, DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4)
  • (DIM_TYPE DIM1, DIM_TYPE DIM2, DIM_TYPE DIM3, DIM_TYPE DIM4, DATA_TYPE* INPLACE_FARRAY4)

Эти типомэпы теперь проверяют, чтобы аргументы INPLACE_ARRAY использовали родной порядок байтов. Если нет, то генерируется исключение.

Также есть «плоский» массив на месте для ситуаций, когда вы хотите изменить или обработать каждый элемент независимо от числа измерений. Одним примером является функция «квантования», которая квантует каждый элемент массива на месте, будь то 1D, 2D или любое другое. Этот вариант проверяет непрерывность, но допускает порядок C или Fortran.

ND:

  • (DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)

Массивы argout

Массивы argout — это массивы, которые появляются в входных аргументах в C, но фактически являются выходными массивами. Этот шаблон часто встречается, когда имеется более одной выходной переменной, и одного возвращаемого аргумента недостаточно. В Python обычный способ возвращения нескольких аргументов — упаковать их в последовательность (кортеж, список и т.д.) и вернуть эту последовательность. Именно это делают типомэпы argout. Если функция с оболочкой, использующая эти типомэпы argout, возвращает более одного аргумента, они упаковываются в кортеж или список в зависимости от версии Python. Пользователь Python не передает эти массивы, они просто возвращаются. В случае, когда указано измерение, пользователь Python должен указать это измерение как аргумент. Подписи argout:

1D:

  • ( DATA_TYPE ARGOUT_ARRAY1[ANY] )
  • ( DATA_TYPE* ARGOUT_ARRAY1, int DIM1 )
  • ( int DIM1, DATA_TYPE* ARGOUT_ARRAY1 )

2D:

  • ( DATA_TYPE ARGOUT_ARRAY2[ANY][ANY] )

3D:

  • ( DATA_TYPE ARGOUT_ARRAY3[ANY][ANY][ANY] )

4D:

  • ( DATA_TYPE ARGOUT_ARRAY4[ANY][ANY][ANY][ANY] )

Они обычно используются в ситуациях, когда в C/C++ вы выделяете массив(ы) в куче и вызываете функцию для заполнения значений массива(ов). В Python массивы выделяются для вас и возвращаются как новые объекты массивов.

Обратите внимание, что мы поддерживаем DATA_TYPE* типомэпы argout в 1D, но не в 2D или 3D. Это связано с особенностями синтаксиса типомэпов SWIG и избежать этого нельзя. Обратите внимание, что для этих типов типомэпов 1D функция Python будет принимать единственный аргумент, представляющий DIM1.

Массивы argout view

Массивы argoutview используются в тех случаях, когда ваш код C предоставляет вам представление своих внутренних данных и не требует выделения памяти пользователем. Это может быть опасно. Практически нет способа гарантировать, что внутренние данные из кода C будут существовать в течение всего срока существования массива NumPy, который их оборачивает. Если пользователь уничтожит объект, предоставляющий представление данных, до уничтожения массива NumPy, то использование этого массива может привести к неверным обращениям к памяти или ошибкам сегментации. Тем не менее, в ситуациях работы с большими наборами данных у вас просто нет другого выбора.

Код C, подлежащий оболочке для массивов argoutview, характеризуется указателями: указателями на измерения и двойными указателями на данные, чтобы эти значения могли быть возвращены пользователю. Следовательно, подписи типомэпов argoutview:

1D:

  • ( DATA_TYPE** ARGOUTVIEW_ARRAY1, DIM_TYPE* DIM1 )
  • ( DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEW_ARRAY1 )

2D:

  • ( DATA_TYPE** ARGOUTVIEW_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2 )
  • ( DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_ARRAY2 )
  • ( DATA_TYPE** ARGOUTVIEW_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2 )
  • ( DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEW_FARRAY2 )

3D:

  • ( DATA_TYPE** ARGOUTVIEW_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
  • ( DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_ARRAY3)
  • ( DATA_TYPE** ARGOUTVIEW_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
  • ( DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEW_FARRAY3)

4D:

  • (DATA_TYPE** ARGOUTVIEW_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEW_ARRAY4)
  • (DATA_TYPE** ARGOUTVIEW_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEW_FARRAY4)

Обратите внимание, что массивы со жёстко заданными измерениями не поддерживаются. Они не могут следовать подписям двойных указателей этих типомэпов.

Массивы argout view с управлением памятью

Недавним дополнением к numpy.i являются типомэпы, которые разрешают массивы argout с представлениями в управляемой памяти. См. обсуждение здесь.

1D:

  • (DATA_TYPE** ARGOUTVIEWM_ARRAY1, DIM_TYPE* DIM1)
  • (DIM_TYPE* DIM1, DATA_TYPE** ARGOUTVIEWM_ARRAY1)

2D:

  • (DATA_TYPE** ARGOUTVIEWM_ARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_ARRAY2)
  • (DATA_TYPE** ARGOUTVIEWM_FARRAY2, DIM_TYPE* DIM1, DIM_TYPE* DIM2)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DATA_TYPE** ARGOUTVIEWM_FARRAY2)

3D:

  • (DATA_TYPE** ARGOUTVIEWM_ARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEWM_ARRAY3)
  • (DATA_TYPE** ARGOUTVIEWM_FARRAY3, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DATA_TYPE** ARGOUTVIEWM_FARRAY3)

4D:

  • (DATA_TYPE** ARGOUTVIEWM_ARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_ARRAY4)
  • (DATA_TYPE** ARGOUTVIEWM_FARRAY4, DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4)
  • (DIM_TYPE* DIM1, DIM_TYPE* DIM2, DIM_TYPE* DIM3, DIM_TYPE* DIM4, DATA_TYPE** ARGOUTVIEWM_FARRAY4)

Выходные массивы

Файл интерфейса numpy.i не поддерживает типомэпы для выходных массивов по нескольким причинам. Во-первых, аргументы возврата C/C++ ограничены одним значением. Это предотвращает получение информации о размерах общим способом. Во-вторых, массивы с жёстко заданными длинами не допускаются в качестве аргументов возврата. Другими словами:

double[3] newVector(double x, double y, double z);

не является допустимым синтаксисом C/C++. Поэтому мы не можем предоставить типомэпы вида:

%numpy_typemaps(bool, NPY_BOOL, int)

Если вы столкнулись со случаем, когда функция или метод возвращают указатель на массив, ваш лучший вариант — написать свою версию функции, которая должна быть обернута, либо с использованием %extend в случае методов класса, либо с использованием %ignore и %rename в случае функций.

Другие распространённые типы: bool

Обратите внимание, что тип C++ bool не поддерживается в списке в разделе «Доступные типомэпы». Булевы значения NumPy — это один байт, в то время как C++ bool — четыре байта (по крайней мере, на моей системе). Поэтому:

%numpy_typemaps(bool, NPY_BOOL, int)

приведёт к типомэпам, которые создадут код, ссылающийся на неверную длину данных. Вы можете реализовать следующее макроподстановку:

%numpy_typemaps(bool, NPY_UINT, int)

для исправления проблемы с длиной данных, и Входные массивы будут работать нормально, но Массивы на месте могут не пройти проверку типов.

Другие распространённые типы: complex

Автоматические преобразования типомэпов для комплексных типов с плавающей точкой также не поддерживаются. Это связано с тем, что Python и NumPy написаны на C, в котором нет собственных комплексных типов. Python и NumPy реализуют свои собственные (по существу эквивалентные) struct определения для комплексных переменных:

/* Python */
typedef struct {double real; double imag;} Py_complex;

/* NumPy */
typedef struct {float  real, imag;} npy_cfloat;
typedef struct {double real, imag;} npy_cdouble;

Мы могли бы реализовать:

%numpy_typemaps(Py_complex , NPY_CDOUBLE, int)
%numpy_typemaps(npy_cfloat , NPY_CFLOAT , int)
%numpy_typemaps(npy_cdouble, NPY_CDOUBLE, int)

что обеспечило бы автоматические преобразования типов для массивов типа Py_complex, npy_cfloat и npy_cdouble. Однако, маловероятно, что будет какой-либо независимый (не Python, не NumPy) код приложения, который люди использовали бы SWIG для создания интерфейса Python, который также использовал эти определения для комплексных типов. Скорее всего, эти приложения определят свои собственные типы комплексных чисел или, в случае C++, будут использовать std::complex. Предполагая, что эти структуры данных совместимы с Python и NumPy типами комплексных чисел, %numpy_typemap подстановки, как указано выше (заменив первый аргумент типом комплексных чисел пользователя), должны работать.

Массивы NumPy и скаляры SWIG

SWIG имеет сложную проверку типов для числовых типов. Например, если ваша C/C++ процедура ожидает целое число в качестве входного значения, код, сгенерированный SWIG, будет проверять как целые числа Python, так и длинные целые числа Python и генерировать ошибку переполнения, если предоставленное целое число Python слишком велико, чтобы его можно было преобразовать в целое число C. С введением скалярных массивов NumPy в ваш код Python, вы, возможно, извлечёте целое число из массива NumPy и попытаетесь передать его в обернутую SWIG-ом C/C++ функцию, которая ожидает int, но проверка типов SWIG не распознает скаляр массива NumPy как целое число. (Часто это на самом деле работает — это зависит от того, распознаёт ли NumPy тип целого числа, который вы используете, как наследуемый от типа целого числа Python на вашей платформе. Иногда это означает, что код, работающий на 32-битной машине, не будет работать на 64-битной машине.)

Если вы получите ошибку Python, которая выглядит следующим образом:

TypeError: in method 'MyClass_MyMethod', argument 2 of type 'int'

и аргумент, который вы передаёте, является целым числом, извлечённым из массива NumPy, то вы столкнулись с этой проблемой. Решение заключается в изменении системы преобразования типов SWIG для принятия скаляров массивов NumPy в дополнение к стандартным целочисленным типам. К счастью, эта возможность предоставлена для вас. Просто скопируйте файл:

pyfragments.swg

в рабочую директорию сборки вашего проекта, и эта проблема будет решена. Рекомендуется сделать это в любом случае, так как это только увеличивает возможности вашего Python-интерфейса.

Почему есть второй файл?

Система проверки типов и преобразования SWIG — это сложная комбинация C-макросов, макросов SWIG, типомэпов SWIG и фрагментов SWIG. Фрагменты — это способ условно вставить код в ваш файл обертки, если он нужен, и не вставлять его, если он не нужен. Если несколько типомэпов требуют одного и того же фрагмента, фрагмент вставляется в ваш код обертки только один раз.

Есть фрагмент для преобразования целого числа Python в C long. Есть другой фрагмент, который преобразует целое число Python в C int, вызывающий процедуру, определённую в фрагменте long. Мы можем внести желаемые изменения, изменив определение для фрагмента long. SWIG определяет активное определение фрагмента по принципу «первый пришёл, первый обслужен». То есть, нам нужно определить фрагмент для преобразований long до того, как SWIG сделает это внутренне. SWIG позволяет нам это сделать, поместив определения наших фрагментов в файл pyfragments.swg. Если мы поместим новые определения фрагментов в numpy.i, они будут проигнорированы.

Вспомогательные функции

Файл numpy.i содержит несколько макросов и процедур, которые он использует во внутренней работе для построения своих typemap. Однако эти функции могут быть полезны и в других частях вашего интерфейсного файла. Эти макросы и процедуры реализованы как фрагменты, которые кратко описаны в предыдущем разделе. Если вы попытаетесь использовать один или несколько из следующих макросов или функций, но ваш компилятор сообщит, что не распознаёт символ, то вам необходимо заставить эти фрагменты появиться в вашем коде, используя:

%fragment("NumPy_Fragments");

в вашем интерфейсном файле SWIG.

Макросы

is_array(a)

Оценивается как истина, если a не NULL и может быть приведено к типу PyArrayObject*.

array_type(a)

Оценивается как целочисленный код типа данных a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_numdims(a)

Оценивается как целое число измерений a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_dimensions(a)

Оценивается как массив типа npy_intp и длины array_numdims(a), предоставляющий длины всех измерений a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_size(a,i)

Оценивается как размер i-го измерения a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_strides(a)

Оценивается как массив типа npy_intp и длины array_numdims(a), предоставляющий шаги всех измерений a, предполагая, что a может быть приведён к типу PyArrayObject*.

Шаг — это расстояние в байтах между элементом и его непосредственным соседом вдоль той же оси.

array_stride(a,i)

Оценивается как i-й шаг a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_data(a)

Оценивается как указатель типа void* на буфер данных a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_descr(a)

Возвращает ссылку на свойство dtype (PyArray_Descr*) a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_flags(a)

Возвращает целое число, представляющее флаги a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_enableflags(a,f)

Устанавливает флаг, представленный f для a, предполагая, что a может быть приведён к типу PyArrayObject*.

array_is_contiguous(a)

Оценивается как истина, если a является непрерывным массивом. Эквивалентно (PyArray_ISCONTIGUOUS(a)).

array_is_native(a)

Оценивается как истина, если буфер данных a использует родной порядок байтов. Эквивалентно (PyArray_ISNOTSWAPPED(a)).

array_is_fortran(a)

Оценивается как истина, если a имеет порядок Fortran.

Процедуры

pytype_string()

Тип возвращаемого значения: const char*

Аргументы:

  • PyObject* py_obj, общий объект Python.

Возвращает строку, описывающую тип py_obj.

typecode_string()

Тип возвращаемого значения: const char*

Аргументы:

  • int typecode, целочисленный код типа NumPy.

Возвращает строку, описывающую тип, соответствующий коду типа NumPy typecode.

type_match()

Тип возвращаемого значения: int

Аргументы:

  • int actual_type, код типа NumPy массива NumPy.
  • int desired_type, желаемый код типа NumPy.

Убедитесь, что actual_type совместим с desired_type.

Например, это позволяет символам и байтам, или типам int и long, совпадать.

Теперь это эквивалентно PyArray_EquivTypenums().

obj_to_array_no_conversion()

Тип возвращаемого значения: PyArrayObject*

Аргументы:

  • PyObject* input, общий объект Python.
  • int typecode, желаемый код типа NumPy.

Приводит input к типу PyArrayObject* если это возможно и гарантирует, что он имеет тип typecode.

Если input нельзя привести к этому типу или код типа typecode неверен, устанавливается ошибка Python и возвращается NULL.

obj_to_array_allow_conversion()

Тип возвращаемого значения: PyArrayObject*

Аргументы:

  • PyObject* input, общий объект Python.
  • int typecode, желаемый код типа NumPy результирующего массива.
  • int* is_new_object, возвращает значение 0, если преобразование не выполнено, иначе 1.

Преобразует input в массив NumPy с заданным typecode.

При успехе возвращает действительный PyArrayObject* с правильным типом. При ошибке строка ошибки Python устанавливается, и процедура возвращает NULL.

make_contiguous()

Тип возвращаемого значения: PyArrayObject*

Аргументы:

  • PyArrayObject* ary, массив NumPy.
  • int* is_new_object, возвращает значение 0, если преобразование не выполнено, иначе 1.
  • int min_dims, минимальное допустимое число измерений.
  • int max_dims, максимальное допустимое число измерений.

Проверяет, является ли ary непрерывным. Если да, возвращает указатель входного массива и помечает его как не новый объект. Если нет, создаёт новый PyArrayObject*, используя исходные данные, помечает его как новый объект и возвращает указатель.

make_fortran()

Тип возвращаемого значения: PyArrayObject*

Аргументы

  • PyArrayObject* ary, массив NumPy.
  • int* is_new_object, возвращает значение 0, если преобразование не выполнено, иначе 1.

Проверяет, является ли ary непрерывным в порядке Fortran. Если да, возвращает указатель входного массива и помечает его как не новый объект. Если нет, создаёт новый PyArrayObject*, используя исходные данные, помечает его как новый объект и возвращает указатель.

obj_to_array_contiguous_allow_conversion()

Тип возвращаемого значения: PyArrayObject*

Аргументы:

  • PyObject* input, общий объект Python.
  • int typecode, желаемый код типа NumPy результирующего массива.
  • int* is_new_object, возвращает значение 0, если преобразование не выполнено, иначе 1.

Преобразует input в непрерывный PyArrayObject* указанного типа. Если входной объект не является непрерывным PyArrayObject*, будет создан новый.

obj_to_array_fortran_allow_conversion()

Тип возвращаемого значения: PyArrayObject*

Аргументы:

  • PyObject* input, общий объект Python.
  • int typecode, желаемый код типа NumPy результирующего массива.
  • int* is_new_object, возвращает значение 0, если преобразование не выполнено, иначе 1.

Преобразует input в массив Fortran с непрерывным PyArrayObject* указанного типа. Если входной объект не является непрерывным в порядке Fortran PyArrayObject*, будет создан новый.

require_contiguous()

Тип возвращаемого значения: int

Аргументы:

  • PyArrayObject* ary, массив NumPy.

Проверяет, является ли ary непрерывным. Если да, возвращает 1. В противном случае устанавливает ошибку Python и возвращает 0.

require_native()

Тип возвращаемого значения: int

Аргументы:

  • PyArray_Object* ary, массив NumPy.

Требует, чтобы ary не был с заменой байтов. Если массив не с заменой байтов, возвращает 1. В противном случае устанавливает ошибку Python и возвращает 0.

require_dimensions()

Тип возвращаемого значения: int

Аргументы:

  • PyArrayObject* ary, массив NumPy.
  • int exact_dimensions, желаемое число измерений.

Требует, чтобы ary имел указанное число измерений. Если массив имеет указанное число измерений, возвращает 1. В противном случае устанавливает ошибку Python и возвращает 0.

require_dimensions_n()

Тип возвращаемого значения: int

Аргументы:

  • PyArrayObject* ary, массив NumPy.
  • int* exact_dimensions, массив целых чисел, представляющих допустимые числа измерений.
  • int n, длина exact_dimensions.

Требует, чтобы ary имел одно из указанного списка чисел измерений. Если массив имеет одно из указанных чисел измерений, возвращает 1. В противном случае устанавливает строку ошибки Python и возвращает 0.

require_size()

Тип возвращаемого значения: int

Аргументы:

  • PyArrayObject* ary, массив NumPy.
  • npy_int* size, массив, представляющий желаемые длины каждого измерения.
  • int n, длина size.

Требует, чтобы ary имел указанную форму. Если массив имеет указанную форму, возвращает 1. В противном случае устанавливает строку ошибки Python и возвращает 0.

require_fortran()

Тип возвращаемого значения: int

Аргументы:

  • PyArrayObject* ary, массив NumPy.

Требует, чтобы данный PyArrayObject был упорядочен по Fortran. Если PyArrayObject уже упорядочен по Fortran, ничего не делает. В противном случае устанавливает флаг упорядочения по Fortran и пересчитывает шаги.

За пределами предоставленных typemap

Существует множество ситуаций с массивами C или C++/NumPy, которые не покрываются простым %include "numpy.i" и последующими %apply директивами.

Пример

Рассмотрим разумный прототип функции скалярного произведения:

double dot(int len, double* vec1, double* vec2);

Интерфейс Python, который мы хотим:

def dot(vec1, vec2):
    """
    dot(PyObject,PyObject) -> double
    """

Проблема здесь заключается в том, что есть один аргумент измерения и два аргумента массива, а наши typemaps настроены для измерений, которые применяются к одному массиву (на самом деле, SWIG не предоставляет механизма для связывания len с vec2 , принимающего два входных аргумента Python). Рекомендуемое решение таково:

%apply (int DIM1, double* IN_ARRAY1) {(int len1, double* vec1),
                                      (int len2, double* vec2)}
%rename (dot) my_dot;
%exception my_dot {
    $action
    if (PyErr_Occurred()) SWIG_fail;
}
%inline %{
double my_dot(int len1, double* vec1, int len2, double* vec2) {
    if (len1 != len2) {
        PyErr_Format(PyExc_ValueError,
                     "Arrays of lengths (%d,%d) given",
                     len1, len2);
        return 0.0;
    }
    return dot(len1, vec1, vec2);
}
%}

Если заголовочный файл, содержащий прототип для double dot() , также содержит другие прототипы, которые вы хотите обернуть, так что вам необходимо %include этот заголовочный файл, то вам также потребуется директива %ignore dot; , размещенная после %rename и перед %include директивами. Либо, если функция в вопросе является методом класса, вы захотите использовать %extend вместо %inline в дополнение к %ignore.

Замечание по обработке ошибок: Обратите внимание, что my_dot возвращает double , но также может вызвать ошибку Python. Результирующая обернутая функция вернет представление Python числа с плавающей точкой 0,0, когда длины векторов не совпадают. Поскольку это не NULL, интерпретатор Python не будет знать, как проверить ошибку. По этой причине мы добавляем директиву %exception выше для my_dot , чтобы получить желаемое поведение (обратите внимание, что $action — это макрос, который расширяется до допустимого вызова my_dot). В общем случае вы, вероятно, захотите написать макрос SWIG для выполнения этой задачи.

Другие ситуации

Существуют и другие ситуации обёртывания, в которых numpy.i может быть полезным, когда вы с ними столкнётесь.

  • В некоторых ситуациях возможно использование макроса %numpy_typemaps для реализации typemaps для ваших собственных типов. См. разделы Другие распространённые типы: bool или Другие распространённые типы: complex для примеров. Ещё одна ситуация — если ваши измерения имеют тип, отличный от int (например, long):

    %numpy_typemaps(double, NPY_DOUBLE, long)
    
  • Вы можете использовать код в numpy.i для написания собственных typemaps. Например, если у вас есть пятимерный массив в качестве аргумента функции, вы можете скопировать и вставить соответствующие typemaps для четырёхмерных массивов в свой интерфейсный файл. Модификации для четвёртого измерения будут тривиальными.
  • Иногда лучший подход — использовать директиву %extend для определения новых методов для ваших классов (или перегрузки существующих), которые принимают PyObject* (который либо является, либо может быть преобразован в PyArrayObject* ), а не указатель на буфер. В этом случае вспомогательные функции в numpy.i могут быть очень полезны.
  • Написание typemaps может быть несколько неинтуитивным. Если у вас есть конкретные вопросы о написании typemaps SWIG для NumPy, разработчики numpy.i следят за почтовыми списками Numpy-discussion и Swig-user.

Заключительное замечание

Когда вы используете директиву %apply , как обычно необходимо для использования numpy.i, она будет действовать до тех пор, пока вы не сообщите SWIG, что её не должно быть. Если аргументы функций или методов, которые вы обертываете, имеют общие имена, такие как length или vector, эти typemaps могут применяться в ситуациях, которых вы не ожидаете или не хотите. Поэтому всегда рекомендуется добавить директиву %clear после того, как вы закончите с конкретным typemap:

%apply (double* IN_ARRAY1, int DIM1) {(double* vector, int length)}
%include "my_header.h"
%clear (double* vector, int length);

В целом, вы должны нацеливать эти подписи typemap конкретно там, где вам нужно, а затем очищать их после завершения.

Резюме

Из коробки numpy.i предоставляет typemaps, которые поддерживают преобразование между массивами NumPy и C-массивами:

  • которые могут быть одним из 12 различных скалярных типов: signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long, float и double.
  • которые поддерживают 74 различные подписи аргументов для каждого типа данных, включая:

    • Одномерные, двумерные, трёхмерные и четырёхмерные массивы.
    • Только входные, на месте, argout, argoutview и управляемые памятью argoutview поведения.
    • Жестко заданные измерения, спецификация буфера данных и измерений, и спецификация измерений и буфера данных.
    • Поддержка как C-порядка («последнее измерение быстрее»), так и Fortran-порядка («первое измерение быстрее») для 2D, 3D и 4D массивов.

Файл интерфейса numpy.i также предоставляет дополнительные инструменты для разработчиков обёртки, включая:

  • Макрос SWIG (%numpy_typemaps) с тремя аргументами для реализации 74 подписей аргументов для выбора пользователем (1) типа C данных, (2) типа данных NumPy (предполагая, что они совпадают) и (3) типа измерения.
  • Четырнадцать C-макросов и пятнадцать C-функций, которые могут быть использованы для написания специализированных typemaps, расширений или встроенных функций, обрабатывающих случаи, не охваченные предоставленными typemaps. Обратите внимание, что макросы и функции запрограммированы специально для работы с NumPy C/API независимо от номера версии NumPy, как до, так и после устаревания некоторых аспектов API после версии 1.6.

© 2005–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/reference/swig.interface-file.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API