numpy.i:numpy的swig接口文件

介绍

简单的包装器和接口生成器(或 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 允许通过调用的机制定义这些类型的转换 类型映射 . 本文档提供了有关如何使用的信息 numpy.i ,A SWIG 定义一系列类型映射的接口文件,旨在使上面描述的与数组相关的转换类型相对简单地实现。例如,假设 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"

类型映射是从一个或多个函数参数列表中按类型或类型和名称键入的。我们将参考如下列表: 签名 . 由定义的许多类型映射之一 numpy.i 在上面使用并且有签名 (double* IN_ARRAY1, int DIM1) . 参数名称旨在表明 double* 参数是一维的输入数组, int 表示该维度的大小。这正是 rms 原型。

最有可能的是,要包装的实际原型没有参数名 IN_ARRAY1DIM1 . 我们使用 SWIG %apply 指令为类型为的一维输入数组应用类型映射 double 到实际使用的原型 rms . 使用 numpy.i 因此,有效地说,需要知道哪些类型的地图是可用的,它们做什么。

A 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 }

排版图来自 numpy.i 负责以下代码行:12-20、25和30。第10行解析 rms 功能。从格式字符串 "O:rms" 我们可以看到,参数列表应该是一个单独的python对象(由 O 其指针存储在 obj0 . 多个函数,由提供 numpy.i 调用,以进行并检查(可能的)从通用python对象到numpy数组的转换。这些功能在本节中进行了解释。 Helper Functions 但希望他们的名字是不言自明的。在12号线我们使用 obj0 构造一个numpy数组。在第17行,我们检查了结果的有效性:它是非空的,并且它有一个任意长度的一维。一旦验证了这些状态,我们就提取第19行和第20行中的数据缓冲区和长度,以便在第22行调用底层的C函数。第25行对我们已经创建了不再需要的新数组的情况执行内存管理。

此代码有大量的错误处理。注意 SWIG_fail 是一个宏 goto fail ,参考第28行的标签。如果用户提供的参数个数不正确,这将在第10行被捕获。如果numpy数组的构造失败或生成的数组的维数不正确,那么这些错误会在第17行被捕获。最后,如果检测到错误,那么在第30行仍然可以正确地管理内存。

注意,如果C函数签名的顺序不同:

double rms(int n, double* seq);

那个 SWIG 将与上面给出的类型映射签名与的参数列表不匹配 rms . 幸运的是, numpy.i 有一组带有最后给出的数据指针的类型映射::

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

这只是改变了 arg1arg2 在上面生成的代码的第3行和第4行中,以及它们在第19行和第20行中的分配。

使用NUMPY

这个 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() .

可用的排版图

由提供的类型映射指令 numpy.i 对于不同数据类型的数组,例如 doubleint 以及不同类型的尺寸,比如 intlong ,除了C和numpy类型规范外,彼此相同。因此,类型映射是通过宏实现的(通常是在幕后)::

%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 用于实现以下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 它应该是许多类型的整数之一。

类型映射签名在很大程度上不同于为缓冲区指针指定的名称。姓名与 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排序。

钕:

  • (DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)

阵列天线

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++中的情况下,您将在堆上分配一个(n)数组,并调用函数来填充数组值。在python中,数组被分配给您,并作为新的数组对象返回。

注意我们支持 DATA_TYPE* argout类型映射为一维,但不是二维或三维。这是因为 SWIG 类型映射语法,无法避免。注意,对于这些类型的1d类型映射,python函数将采用一个参数来表示 DIM1 .

argout视图数组

argoutview数组用于C代码向您提供其内部数据的视图,并且不需要用户分配任何内存。这可能很危险。几乎没有办法保证来自C代码的内部数据将在封装它的numpy数组的整个生命周期内保持存在。如果用户在销毁numpy数组之前销毁提供数据视图的对象,那么使用该数组可能会导致错误的内存引用或分段错误。然而,在处理大型数据集的情况下,您没有其他选择。

argoutview数组要包装的C代码的特征是指针:指向维度的指针和指向数据的双指针,以便可以将这些值传递回用户。因此,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数组和视图进入托管内存的类型映射。查看讨论 here .

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 中的列表不支持 Available Typemaps 部分。NUMPY BOOLS是一个字节,而C++ bool 是四个字节(至少在我的系统上)。因此:

%numpy_typemaps(bool, NPY_BOOL, int)

将导致生成引用不正确数据长度的代码的类型映射。可以实现以下宏扩展::

%numpy_typemaps(bool, NPY_UINT, int)

解决数据长度问题,以及 Input Arrays 会很好的,但是 In-Place Arrays 类型检查可能失败。

其他常见类型:复杂

也不自动支持复杂浮点类型的类型映射转换。这是因为python和numpy是用C编写的,而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_complexnpy_cfloatnpy_cdouble . 然而,似乎不太可能有任何独立的(非python,非numpy)应用程序代码供人们使用。 SWIG 为了生成一个python接口,它还为复杂类型使用了这些定义。更有可能的是,这些应用程序代码将定义它们自己的复杂类型,或者在C++的情况下使用。 std::complex . 假设这些数据结构与python和numpy复杂类型兼容, %numpy_typemap 如上所述的扩展(用用户的复杂类型替换第一个参数)应该可以工作。

numpy数组scalars和swig

SWIG 对数字类型进行复杂的类型检查。例如,如果C/C++例程期望一个整数作为输入,则由 SWIG 将检查python整数和python long整数,如果提供的python整数太大,无法向下转换为C整数,则会引发溢出错误。在python代码中引入numpy标量数组后,您可能会从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 文件包含几个宏和例程,这些宏和例程在内部用于构建其类型映射。但是,这些函数在接口文件的其他地方可能很有用。这些宏和例程是作为片段实现的,在前一节中简要介绍了这些片段。如果试图使用以下一个或多个宏或函数,但编译器抱怨它无法识别该符号,则需要强制这些片段在代码中显示,方法是:

%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 到A 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 使用给定的 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是连续的。如果是,则返回输入指针并将其标记为非新对象。如果它不是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 到邻近的要塞 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 nsize 的长度。

要求 ary 具有指定形状。如果数组具有指定的形状,则返回1。否则,设置python错误字符串并返回0。

require_fortran()

返回类型: int

参数:

  • PyArrayObject* ary ,一个 NumPy 的数组。

要求给出 PyArrayObject 将被Fortran命令。如果 PyArrayObject 已被Fortran命令,不执行任何操作。否则,设置Fortran排序标志并重新计算步幅。

超出提供的排版图

有许多C或C++数组/ NUMPY数组的情况不被简单的覆盖 %include "numpy.i" 随后 %apply 指令。

一个常见的例子

考虑一个点积函数的合理原型:

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

我们想要的python接口是:

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

这里的问题是有一个维度参数和两个数组参数,我们的类型映射是为应用于单个数组的维度设置的(实际上, 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 返回A double 但它也会引发一个python错误。当向量长度不匹配时,结果包装函数将返回python float表示0.0。因为这不是 NULL ,python解释器将不知道是否要检查错误。因此,我们将 %exception 上述指令 my_dot 为了得到我们想要的行为(注意 $action 是一个扩展为有效调用的宏 my_dot )一般来说,你可能会想写一个 SWIG 执行此任务的宏。

其他情况

还有其他包装情况 numpy.i 当你遇到他们时可能会有所帮助。

  • 在某些情况下,您可以使用 %numpy_typemaps 宏为您自己的类型实现类型映射。见 Other Common Types: boolOther Common Types: complex 示例部分。另一种情况是,如果您的维度不是 int (说 long 例如:

    %numpy_typemaps(double, NPY_DOUBLE, long)
    
  • 您可以在 numpy.i 写你自己的排版图。例如,如果您有一个作为函数参数的五维数组,那么您可以将适当的四维类型映射剪切并粘贴到接口文件中。对第四维度的修改是微不足道的。

  • 有时,最好的方法是使用 %extend 为类定义新方法(或重载现有方法)的指令 PyObject* (或者可以转换为 PyArrayObject* )而不是指向缓冲区的指针。在这种情况下,帮助程序在 numpy.i 可能非常有用。

  • 书写排版图可能有点不直观。如果你有关于写作的具体问题 SWIG numpy的类型映射,开发人员 numpy.i 做监控 Numpy-discussionSwig-user 邮件列表。

最后一个音符

当你使用 %apply 指令,通常需要使用 numpy.i ,它将一直有效,直到你告诉我 SWIG 不应该这样。如果要包装的函数或方法的参数具有公用名,例如 lengthvector ,这些类型映射可能会在您不期望或不希望的情况下应用。因此,添加 %clear 完成特定类型映射后的指令:

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

一般来说,您应该将这些类型映射签名作为目标,特别是在需要它们的地方,然后在完成之后清除它们。

总结

走出盒子, numpy.i 提供支持numpy数组和c数组之间转换的类型映射:

  • 它可以是12种不同的标量类型之一: signed charunsigned charshortunsigned shortintunsigned intlongunsigned longlong longunsigned long longfloatdouble .

  • 它为每种数据类型支持74个不同的参数签名,包括:

    • 一维、二维、三维和四维阵列。

    • 仅输入、就地、argout、argoutview和内存管理的argoutview行为。

    • 硬编码尺寸、数据缓冲器、尺寸规格、尺寸、数据缓冲器规格。

    • C排序(“最快的最后一个维度”)或Fortran排序(“最快的第一个维度”)都支持二维、三维和4d数组。

这个 numpy.i 接口文件还为包装开发人员提供了其他工具,包括:

  • A SWIG 宏 (%numpy_typemaps )有三个参数用于实现74个参数签名,供用户选择(1)C数据类型,(2)numpy数据类型(假设它们匹配),以及(3)维度类型。

  • 14个C宏和15个C函数,可用于编写专门的类型映射、扩展或内联函数,以处理所提供类型映射未涵盖的情况。请注意,宏和函数是专门为使用numpy c/api而编码的,不管numpy版本号是什么,在版本1.6之后的api的某些方面被弃用之前和之后。