numpy.i: NumPy 的 SWIG 接口文件#
简介#
简单封装器和接口生成器(或 SWIG)是一个强大的工具,用于生成封装代码,以便与各种脚本语言进行接口。 SWIG 可以解析头文件,并且仅使用代码原型,就可以创建到目标语言的接口。但是 SWIG 并非无所不能。例如,它无法从原型中知道
double rms(double* seq, int n);
seq
究竟是什么。它是一个要就地更改的单个值吗?它是一个数组吗?如果是数组,它的长度是多少?它是仅输入?仅输出?输入输出?SWIG 无法确定这些细节,也不试图这样做。
如果我们设计了 rms
,我们可能会将其设计为一个例程,该例程采用长度为 n
的 double
值的仅输入数组(称为 seq
),并返回均方根。但是,SWIG 的默认行为是创建一个可以编译的封装函数,但从脚本语言中使用的方式与 C 例程的预期方式几乎不可能一致。
对于 Python,处理连续(或技术上是跨步)的同质数据块的首选方法是使用 NumPy,它提供对多维数据数组的完全面向对象的访问。因此,rms
函数最符合逻辑的 Python 接口将是(包括文档字符串)
def rms(seq):
"""
rms: return the root mean square of a sequence
rms(numpy.ndarray) -> double
rms(list) -> double
rms(tuple) -> double
"""
其中 seq
将是一个 double
值的 NumPy 数组,它的长度 n
将在内部从 seq
中提取,然后再传递给 C 例程。更好的是,由于 NumPy 支持从任意 Python 序列构造数组,因此 seq
本身可能是一个几乎任意的序列(只要每个元素都可以转换为 double
),并且封装代码会在内部将其转换为 NumPy 数组,然后再提取其数据和长度。
SWIG 允许通过一种称为类型映射的机制来定义这些类型的转换。本文档提供了有关如何使用 numpy.i
的信息,这是一个 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_ARRAY1
和 DIM1
。我们使用 SWIG %apply
指令将一维类型 double
输入数组的类型映射应用于 rms
使用的实际原型。因此,有效地使用 numpy.i
需要知道有哪些可用的类型映射以及它们的作用。
包含上面给出的 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 数组的(可能的)转换。这些函数将在 辅助函数 部分中说明,但希望它们的名称是不言自明的。在第 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)};
这仅仅具有在上面生成的代码的第 3 行和第 4 行中切换 arg1
和 arg2
的定义的效果,以及它们在第 19 行和第 20 行中的赋值。
使用 numpy.i#
numpy.i
文件当前位于 numpy
安装目录下的 tools/swig
子目录中。通常,您需要将其复制到您正在开发封装器的目录中。
仅使用单个 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
为不同数据类型的数组(例如 double
和 int
)和不同类型的维度(例如 int
或 long
)提供的类型映射指令彼此相同,除了 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 排序。
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 类型映射,但不支持二维或三维。这是因为 SWIG 类型映射语法的一个怪癖,无法避免。请注意,对于这些类型的一维类型映射,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 数组的类型映射。
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 编写的,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
类型的数组提供自动类型转换。但是,似乎不太可能存在人们会使用 SWIG 生成 Python 接口的任何独立的(非 Python、非 NumPy)应用程序代码,这些代码也使用这些复数类型定义。更可能的情况是,这些应用程序代码将定义自己的复数类型,或者在 C++ 的情况下,使用 std::complex
。假设这些数据结构与 Python 和 NumPy 复数类型兼容,则如上所述的 %numpy_typemap
扩展(用户的复数类型替换为第一个参数)应该可以工作。
NumPy 数组标量和 SWIG#
SWIG 对数值类型有复杂的类型检查。例如,如果您的 C/C++ 例程期望将整数作为输入,则 SWIG 生成的代码将检查 Python 整数和 Python 长整数,如果提供的 Python 整数太大而无法转换为 C 整数,则会引发溢出错误。将 NumPy 标量数组引入您的 Python 代码后,您可以想象从 NumPy 数组中提取一个整数,并尝试将其传递给期望 int
的 SWIG 封装的 C/C++ 函数,但是 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 使用“先到先得”的系统确定片段的活动定义。也就是说,我们需要在 SWIG 内部完成之前定义 long
转换的片段。SWIG 允许我们通过将片段定义放在文件 pyfragments.swg
中来实现这一点。如果我们将新的片段定义放在 numpy.i
中,它们将被忽略。
辅助函数#
numpy.i
文件包含它内部用来构建类型映射的几个宏和例程。但是,这些函数可能在您的接口文件的其他地方很有用。这些宏和例程以片段的形式实现,在前一节中已简要描述。如果您尝试使用以下一个或多个宏或函数,但您的编译器抱怨它无法识别该符号,那么您需要使用以下方法强制这些片段出现在您的代码中:
%fragment("NumPy_Fragments");
在您的 SWIG 接口文件中。
宏#
- is_array(a)
如果
a
非NULL
并且可以强制转换为PyArrayObject*
,则评估为 true。- array_type(a)
如果
a
可以强制转换为PyArrayObject*
,则评估为a
的整数数据类型代码。- array_numdims(a)
如果
a
可以强制转换为PyArrayObject*
,则评估为a
的整数维度数。- array_dimensions(a)
如果
a
可以强制转换为PyArrayObject*
,则评估为类型为npy_intp
且长度为array_numdims(a)
的数组,给出a
的所有维度的长度。- array_size(a,i)
如果
a
可以强制转换为PyArrayObject*
,则评估为a
的第i
个维度的大小。- array_strides(a)
如果
a
可以强制转换为PyArrayObject*
,则评估为类型为npy_intp
且长度为array_numdims(a)
的数组,给出a
的所有维度的步幅。步幅是沿同一轴的元素及其直接相邻元素之间的字节距离。- array_stride(a,i)
如果
a
可以强制转换为PyArrayObject*
,则评估为a
的第i
个步幅。- array_data(a)
如果
a
可以强制转换为PyArrayObject*
,则评估为指向a
的数据缓冲区的void*
类型的指针。- array_descr(a)
如果
a
可以强制转换为PyArrayObject*
,则返回对a
的 dtype 属性(PyArray_Descr*
)的借用引用。- array_flags(a)
如果
a
可以强制转换为PyArrayObject*
,则返回表示a
的标志的整数。- array_enableflags(a,f)
如果
a
可以强制转换为PyArrayObject*
,则设置a
的由f
表示的标志。- array_is_contiguous(a)
如果
a
是连续数组,则评估为 true。等价于(PyArray_ISCONTIGUOUS(a))
。- array_is_native(a)
如果
a
的数据缓冲区使用本机字节顺序,则评估为 true。等价于(PyArray_ISNOTSWAPPED(a))
。- array_is_fortran(a)
如果
a
是 FORTRAN 排序的,则评估为 true。
例程#
- 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
转换为具有给定typecode
的 NumPy 数组。成功时,返回具有正确类型的有效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
转换为指定类型的 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 排序标志并重新计算步长。
超出提供的类型映射#
许多 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
返回一个 double
,但它也可能引发 Python 错误。当向量长度不匹配时,生成的包装函数将返回 0.0 的 Python float 表示形式。由于这不是 NULL
,Python 解释器将不知道要检查错误。因此,我们在上面为 my_dot
添加了 %exception
指令,以获得我们想要的行为(请注意,$action
是一个宏,它被扩展为对 my_dot
的有效调用)。一般来说,您可能需要编写一个 SWIG 宏来执行此任务。
其他情况#
在您遇到它们时,在其他包装情况下,numpy.i
可能会有所帮助。
在某些情况下,您可以使用
%numpy_typemaps
宏为您自己的类型实现类型映射。有关示例,请参阅 其他常见类型:bool 或 其他常见类型:complex 部分。另一种情况是,如果您的维度是int
以外的类型(例如long
)。%numpy_typemaps(double, NPY_DOUBLE, long)
您可以使用
numpy.i
中的代码编写自己的类型映射。例如,如果您的函数参数中有一个五维数组,您可以将适当的四维类型映射复制粘贴到您的接口文件中。对第四维的修改将是微不足道的。有时,最好的方法是使用
%extend
指令为您的类定义新方法(或重载现有方法),这些方法接受一个PyObject*
(它要么是,要么可以转换为PyArrayObject*
),而不是指向缓冲区的指针。在这种情况下,numpy.i
中的辅助例程非常有用。编写类型映射可能有点违反直觉。如果您对为 NumPy 编写 SWIG 类型映射有具体问题,
numpy.i
的开发人员会监控 Numpy-discussion 和 Swig-user 邮件列表。
最后说明#
当您使用 %apply
指令时(通常是使用 numpy.i
所必需的),它将一直有效,直到您告诉 SWIG 它不应该有效为止。如果您要包装的函数或方法的参数具有通用名称,例如 length
或 vector
,这些类型映射可能会在您不期望或不希望的情况下应用。因此,在您完成特定的类型映射后,始终最好添加一个 %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 char
、unsigned char
、short
、unsigned short
、int
、unsigned int
、long
、unsigned long
、long long
、unsigned long long
、float
和double
。支持每种数据类型的 74 种不同的参数签名,包括
一维、二维、三维和四维数组。
仅输入、就地、argout、argoutview 和内存管理 argoutview 行为。
硬编码维度、数据缓冲区后跟维度规范以及维度后跟数据缓冲区规范。
对 2D、3D 和 4D 数组的 C 顺序(“最后维度最快”)或 Fortran 顺序(“第一维度最快”)支持。
numpy.i
接口文件还为包装器开发人员提供了其他工具,包括
一个 SWIG 宏 (
%numpy_typemaps
),它带有三个参数,用于为用户选择的 (1) C 数据类型、(2) NumPy 数据类型(假设它们匹配)和 (3) 维度类型实现 74 个参数签名。十四个 C 宏和十五个 C 函数,可用于编写专门的类型映射、扩展或内联函数,以处理所提供的类型映射未涵盖的情况。请注意,宏和函数专门编码为与 NumPy C/API 一起使用,而与 NumPy 版本号无关,无论是在版本 1.6 之后 API 的某些方面被弃用之前还是之后。