Spec-Zone.ru › NumPy 1.12

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 (или одномерных массивов).

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

Входные массивы определяются как массивы данных, которые передаются в процедуру, но не изменяются на месте или не возвращаются пользователю. Входной массив 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 Видовые массивы

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 Видовые массивы

Недавнее дополнение к 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++. Поэтому мы не можем предоставить типомэпы в формате:

%typemap(out) (TYPE[ANY]);

Если вы столкнулись со случаем, когда функция или метод возвращает указатель на массив, ваш лучший вариант — написать свою версию функции для обёртки, либо с %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 и попытаться передать его в функцию C/C++, обёрнутую SWIG, которая ожидает целое число, но 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)
Вычисляет true, если 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)
Вычисляет true, если a является непрерывным массивом. Эквивалентно (PyArray_ISCONTIGUOUS(a)).
array_is_native(a)
Вычисляет true, если буфер данных a использует родной порядок байтов. Эквивалентно (PyArray_ISNOTSWAPPED(a)).
array_is_fortran(a)
Вычисляет true, если 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
    """

Проблема здесь в том, что есть один аргумент измерения и два аргумента массива, а наши typemap настроены для измерений, которые применяются к одному массиву (на самом деле, 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 для реализации typemap для собственных типов. См. разделы Другие общие типы: bool или Другие общие типы: complex для примеров. Другая ситуация возникает, если ваши измерения имеют тип, отличный от int (например, long):

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

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

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

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

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

Резюме

Из коробки numpy.i предоставляет typemap, которые поддерживают преобразование между массивами 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-функций, которые могут быть использованы для написания специализированных typemap, расширений или встроенных функций, которые обрабатывают случаи, не охваченные предоставленными typemap. Обратите внимание, что макросы и функции закодированы специально для работы с NumPy C/API независимо от версии NumPy, как до, так и после устаревания некоторых аспектов API после версии 1.6.

© 2008–2017 NumPy Developers
Licensed under the NumPy License.
https://docs.scipy.org/doc/numpy-1.12.0/reference/swig.interface-file.html

Spec-Zone.ru

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