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 将在传递给 C 例程之前从 seq 内部提取.更好的是,由于 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"
Typemaps 是基于一个或多个函数参数的列表来键控的,可以是按类型,也可以是按类型和名称.我们将这样的列表称为 签名 .``numpy.i`` 定义的许多 typemaps 之一在上文中使用,并且具有签名 (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 数组的转换.这些函数在 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)};
这仅仅在上述生成代码的第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 charunsigned charshortunsigned shortintunsigned intlongunsigned longlong longunsigned long longfloatdouble
在以下描述中,我们引用了一个通用的 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 typemaps 所做的.如果一个使用这些 argout typemaps 的包装函数有多个返回参数,它们会根据 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* 在1D中的argout typemap,但不支持2D或3D.这是因为 SWIG typemap语法的一个怪癖,无法避免.请注意,对于这些类型的1D typemap,Python函数将接受一个表示 DIM1 的单个参数.
Argout 视图数组#
Argoutview 数组用于当你的 C 代码为你提供其内部数据的视图,并且不需要用户分配任何内存时.这可能很危险.几乎无法保证 C 代码的内部数据在封装它的 NumPy 数组的整个生命周期内仍然存在.如果用户在销毁 NumPy 数组之前销毁了提供数据视图的对象,那么使用该数组可能会导致错误的内存引用或段错误.尽管如此,在处理大数据集的情况下,你可能别无选择.
要为 argoutview 数组包装的 C 代码的特点是指针:指向维度的指针和指向数据的指针,以便这些值可以传递回用户.因此,argoutview typemap 签名是
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)
要解决数据长度问题,`输入数组`_ 将正常工作,但 就地数组 可能会在类型检查时失败.
其他常见类型:复数#
复杂浮点类型的类型映射转换也不自动支持.这是因为 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 的数组的自动类型转换.然而,似乎不太可能有任何独立的(非Python,非NumPy)应用程序代码,人们会使用 SWIG 来生成Python接口,同时也使用这些复杂类型的定义.更有可能的是,这些应用程序代码将定义自己的复杂类型,或者在C++的情况下,使用 std::complex.假设这些数据结构与Python和NumPy的复杂类型兼容,如上所述的 %numpy_typemap 扩展(用户的复杂类型替换第一个参数)应该可以工作.
NumPy 数组标量和 SWIG#
SWIG 对数值类型有复杂的类型检查.例如,如果你的 C/C++ 程序期望输入一个整数,`SWIG`_ 生成的代码将检查 Python 整数和 Python 长整数,如果提供的 Python 整数太大而无法向下转换为 C 整数,则会引发溢出错误.随着在你的 Python 代码中引入 NumPy 标量数组,你可能会从 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*,则评估为真.- array_type(a)
假设
a可以转换为PyArrayObject*,则计算为a的整数数据类型代码.- array_numdims(a)
假设
a可以被转换为PyArrayObject*,则计算a的整数维数.- array_dimensions(a)
计算为一个类型为
npy_intp且长度为array_numdims(a)的数组,给出a的所有维度的长度,假设a可以被转换为PyArrayObject*.- array_size(a,i)
假设
a可以被转换为PyArrayObject*,则计算a的第i维大小.- array_strides(a)
计算为一个类型为
npy_intp的数组,长度为array_numdims(a),给出a的所有维度的步幅,假设a可以被转换为PyArrayObject*.步幅是指在同一轴上,元素与其相邻元素之间的字节距离.- array_stride(a,i)
假设
a可以被转换为PyArrayObject*,则计算a的第i个步幅.- array_data(a)
假设
a可以被转换为PyArrayObject*,则计算结果为一个指向a的数据缓冲区的类型为void*的指针.- array_descr(a)
返回
a的 dtype 属性(PyArray_Descr*)的借用引用,假设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转换为具有给定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 这个头文件,那么你还需要在 %rename 之后和 %include 指令之前放置一个 %ignore dot; 指令.或者,如果所讨论的函数是一个类方法,你将需要使用 %extend 而不是 %inline 以及 %ignore.
关于错误处理的说明: 注意 my_dot 返回一个 double 但它也可以引发一个 Python 错误.生成的包装函数将在向量长度不匹配时返回 0.0 的 Python 浮点数表示.由于这不是 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 种不同的参数签名,包括:
一维、二维、三维和四维数组.
仅输入、就地、输出参数、输出视图参数和内存管理的输出视图参数行为.
硬编码的尺寸、数据缓冲区后跟尺寸的规范,以及尺寸后跟数据缓冲区的规范.
支持 C 排序(”最后一个维度最快”)或 Fortran 排序(”第一个维度最快”)的 2D、3D 和 4D 数组.
numpy.i 接口文件还为包装开发者提供了额外的工具,包括:
一个 SWIG 宏 (
%numpy_typemaps) 带有三个参数,用于实现用户选择的 (1) C 数据类型、(2) NumPy 数据类型(假设它们匹配)和 (3) 维度类型的 74 个参数签名.十四个 C 宏和十五个 C 函数,可用于编写专门的类型映射、扩展或内联函数,处理提供的类型映射未涵盖的情况.请注意,这些宏和函数是专门编码的,以与 NumPy C/API 一起工作,无论 NumPy 版本号如何,在 1.6 版本之后某些 API 方面被弃用之前和之后都能工作.