原文:
numpy.org/doc/
NumPy 安全性
原文:
numpy.org/doc/1.26/reference/security.html
安全问题可以按照项目 README 中描述的方式进行私下报告,也可以在打开问题跟踪器上的新问题时进行报告。Python 安全报告指南是一个很好的资源,其中的注意事项也适用于 NumPy。
NumPy 的维护人员不是安全专家。然而,我们对安全问题持有责任感,对 NumPy 代码库及其使用方式的专家。在发布针对 NumPy 的安全公告之前,请通知我们,因为我们乐意优先处理问题或帮助评估错误的严重性。我们事先不知道的安全公告可能会给所有相关方带来大量工作。
在处理不可信数据时的建议使用 NumPy
可以自由执行 NumPy(或 Python)函数的用户必须被视为具有相同特权的进程/Python 解释器。
也就是说,NumPy 通常能够安全使用来自非特权用户的数据,并且通过安全的 API 函数读取(例如从文本文件或没有 pickle 支持的.npy
文件加载)。恶意值或数据大小不应导致特权升级。
使用不受信任数据时,以下要点可能很有用或应该注意:
- 耗尽内存可能导致内存溢出杀死,这是可能的拒绝服务攻击。可能的原因包括:
- 读取文本文件的函数可能需要比原始输入文件大小多得多的内存。
- 如果用户可以创建任意形状的数组,NumPy 的广播意味着中间或结果数组可能比输入大得多。
- NumPy 结构化 dtype 允许大量的复杂性。幸运的是,当提供意外的结构化 dtype 时,大多数代码会出现优雅失效。然而,代码应该禁止不受信任的用户提供这些(例如通过
.npy
文件)或仔细检查包含的字段以防止嵌套结构/子数组 dtype。 - 通常应考虑通过用户输入传递是不安全的(除了被读取的数据)。例如,
np.dtype(user_string)
或dtype=user_string
。 - 操作的速度可能取决于值和内存顺序可能导致更大的临时内存使用和较慢的执行。这意味着操作可能比简单测试用例显着慢或使用更多内存。
- 在读取数据时,考虑强制规定特定形状(例如一维)或 dtype,如
float64
、float32
或int64
,以减少复杂性。
当处理非常规不可信数据时,建议对分析进行沙盒化,以防潜在的特权升级。如果使用基于 NumPy 的进一步库,则特别建议这样做,因为这些库会增加额外的复杂性和潜在的安全问题。
在处理不受信任数据时的建议使用 NumPy
能够自由执行 NumPy(或 Python)函数的用户必须被视为拥有与进程/Python 解释器相同的特权。
也就是说,NumPy 应该通常是安全的,可用于由特权用户提供的数据,并通过安全的 API 函数读取(例如从文本文件或不支持 pickle 的 .npy
文件加载)。恶意的值或数据大小绝不应导致特权升级。
在处理不受信任的数据时,以下几点可能有用或值得注意:
- 耗尽内存可能导致内存溢出,这是一种可能的拒绝服务攻击。可能的原因包括:
- 读取文本文件的函数可能需要比原始输入文件大得多的内存。
- 如果用户可以创建任意形状的数组,NumPy 的广播意味着中间或结果数组可能远大于输入。
- NumPy 结构化 dtypes 允许大量复杂性。幸运的是,当提供意外的结构化 dtype 时,大多数代码都能优雅地失败。然而,代码应该禁止不受信任的用户提供这些(例如通过
.npy
文件),或仔细检查包含的嵌套结构化/子数组 dtypes 的字段。 - 将用户输入传递下去通常应被视为不安全的(读取的数据除外)。一个例子是
np.dtype(user_string)
或dtype=user_string
。 - 操作的速度可能取决于值,内存顺序可能导致更大的临时内存使用和更慢的执行。这意味着与简单的测试用例相比,操作可能显著更慢或使用更多内存。
- 在读取数据时,考虑强制执行特定的形状(例如一维)或 dtype,如
float64
、float32
或int64
,以减少复杂性。
在处理非平凡的不受信任数据时,建议对分析进行沙箱化以防止潜在的特权升级。如果进一步使用基于 NumPy 的库,这尤其是个好主意,因为这些库增加了额外的复杂性和潜在的安全问题。
NumPy 和 SWIG
原文:
numpy.org/doc/1.26/reference/swig.html
- numpy.i:用于 NumPy 的 SWIG 接口文件
- 介绍
- 使用 numpy.i
- 可用的类型映射
- NumPy 数组标量和 SWIG
- 辅助函数
- 超越提供的类型映射
- 总结
- 测试 numpy.i 类型映射
- 介绍
- 测试组织
- 测试头文件
- 测试源文件
- 测试 SWIG 接口文件
- 测试 Python 脚本
numpy.i:NumPy 的 SWIG 接口文件
原文:
numpy.org/doc/1.26/reference/swig.interface-file.html
简介
用于生成适用于各种脚本语言的包装器代码的强大工具——简单包装器和接口生成器(或SWIG)。SWIG可以解析头文件,并仅使用代码原型,为目标语言创建接口。但SWIG并非无所不能。例如,它无法通过原型了解到:
代码语言:javascript复制double rms(double* seq, int n);
seq
到底是什么。它是一个要原地更改的单个值吗?它是一个数组,如果是的话,它的长度是多少?它只能输入吗?只能输出?输入输出?SWIG无法确定这些细节,并且也不试图这样做。
如果我们设计了rms
,那么我们可能使其成为一个仅接受名为seq
的double
值长度为n
的输入数组的例程,并返回均方根。然而,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允许通过一种称为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 是基于一个或多个函数参数列表进行关键化,可以是类型或类型和名称。我们将这些列表称为签名。numpy.i
定义的众多 typemap 之一被上述使用,具有签名(double* IN_ARRAY1, int DIM1)
。参数名意在表明double*
参数是一个一维输入数组,int
代表该维度的大小。这正是rms
原型中的模式。
大部分情况下,将要包装的实际原型不会有参数名为IN_ARRAY1
和DIM1
。我们使用SWIG的%apply
指令将一维double
类型的输入数组的 typemap 应用到rms
实际原型上。因此,有效地使用numpy.i
需要知道可用的 typemap 以及它们的作用。
一个包含上述SWIG指令的SWIG接口文件将生成类似如下的包装器代码:
代码语言:javascript复制 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
中的 typemaps 负责以下代码行: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
是一个宏,用于引用第 28 行处的标签goto fail
。如果用户提供了错误数量的参数,它会在第 10 行捕捉到。如果构建 NumPy 数组失败或生成具有错误维度数量的数组,则在第 17 行捕捉到这些错误。最后,如果检测到错误,则在第 30 行仍管理内存。
请注意,如果 C 函数签名的顺序不同:
代码语言:javascript复制double rms(int n, double* seq);
那么SWIG将无法与给定rms
参数列表匹配上述 typemap 签名。幸运的是,numpy.i
有一组 typemap,其中数据指针在最后:
%apply (int DIM1, double* IN_ARRAY1) {(int n, double* seq)};
这只是将生成代码中第 3 和第 4 行中的arg1
和arg2
的定义位置互换的效果,并且将它们在第 19 和 20 行的赋值位置也互换。
使用 numpy.i
目前,numpy.i
文件位于 numpy
安装目录下的 tools/swig
子目录中。通常,您会希望将其复制到您开发包装器的目录中。
一个只使用单个 SWIG 接口文件的简单模块应包括以下内容:
代码语言:javascript复制%{
#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
提供的针对不同数据类型(如 双精度浮点型
和 整型
)和维度不同类型(如 整型
或 长整型
)的数组���类型映射指令,除了 C 和 NumPy 类型规格外,是相同的。因此,这些类型映射是通过宏实现的(通常是在幕后):
%numpy_typemaps(DATA_TYPE, DATA_TYPECODE, DIM_TYPE)
可以为适当的 (数据类型, 数据类型代码, DIM_TYPE)
组合调用。例如:
%numpy_typemaps(double, NPY_DOUBLE, int)
%numpy_typemaps(int, NPY_INT , int)
numpy.i
接口文件使用 %numpy_typemaps
宏来为以下 C 数据类型和 int
维度类型实现类型映射:
有符号字符
无符号字符
短整型
无符号短整型
整型
无符号整型
长整型
无符号长整型
长长整型
无符号长长整型
单精度浮点型
双精度浮点型
在以下描述中,我们引用了一个通用的 数据类型
,它可以是上面列出的任何 C 数据类型之一,以及 DIM_TYPE
,应该是多种类型中的其中一种。
类型映射签名在缓冲指针的名称上有很大的不同。带有 FARRAY
的名称用于 Fortran 排序数组,而带有 ARRAY
的名称用于 C 排序(或 1D 数组)。
输入数组
输入数组被定义为传入例程但不在原地更改或返回给用户的数据数组。因此,Python 输入数组几乎可以是任何可以转换为请求类型的数组的 Python 序列(如列表)。输入数组签名为
1D:
( 数据类型 IN_ARRAY1[任意] )
( 数据类型* IN_ARRAY1, int DIM1 )
( int DIM1, 数据类型* IN_ARRAY1 )
2D:
( 数据类型 IN_ARRAY2[任意][任意] )
( 数据类型* IN_ARRAY2, int DIM1, int DIM2 )
( int DIM1, int DIM2, 数据类型* IN_ARRAY2 )
( 数据类型* IN_FARRAY2, int DIM1, int DIM2 )
( int DIM1, int DIM2, 数据类型* IN_FARRAY2 )
3D:
( 数据类型 IN_ARRAY3[任意][任意][任意] )
( 数据类型* IN_ARRAY3, int DIM1, int DIM2, int DIM3 )
( int DIM1, int DIM2, int DIM3, 数据类型* IN_ARRAY3 )
( 数据类型* IN_FARRAY3, int DIM1, int DIM2, int DIM3 )
( int DIM1, int DIM2, int DIM3, 数据类型* IN_FARRAY3 )
4D:
(数据类型 IN_ARRAY4[任意][任意][任意][任意])
(数据类型* IN_ARRAY4, 维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, 维度类型 维度 4)
(维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, , 维度类型 维���4, 数据类型* IN_ARRAY4)
(数据类型* IN_FARRAY4, 维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, 维度类型 维度 4)
(维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, 维度类型 维度 4, 数据类型* IN_FARRAY4)
所列的第一个签名 ( 数据类型 IN_ARRAY[任意] )
是用于具有硬编码维度的一维数组。同样,( 数据类型 IN_ARRAY2[任意][任意] )
用于具有硬编码维度的二维数组,以此类推和三维数组。
就地数组
就地数组被定义为原地修改的数组。输入值可能会被使用,也可能不会被使用,但函数返回时的值是重要的。因此,提供的 Python 参数必须是所需类型的 NumPy 数组。就地签名为
1D:
( 数据类型 INPLACE_ARRAY1[任意] )
( 数据类型* INPLACE_ARRAY1, int 维度 1 )
( int 维度 1, 数据类型* INPLACE_ARRAY1 )
2D:
( 数据类型 INPLACE_ARRAY2[任意][任意] )
( 数据类型* INPLACE_ARRAY2, int 维度 1, int 维度 2 )
( int 维度 1, int 维度 2, 数据类型* INPLACE_ARRAY2 )
( 数据类型* INPLACE_FARRAY2, int 维度 1, int 维度 2 )
( int 维度 1, int 维度 2, 数据类型* INPLACE_FARRAY2 )
3D:
( 数据类型 INPLACE_ARRAY3[任意][任意][任意] )
( 数据类型* INPLACE_ARRAY3, int 维度 1, int 维度 2, int 维度 3 )
( int 维度 1, int 维度 2, int 维度 3, 数据类型* INPLACE_ARRAY3 )
( 数据类型* INPLACE_FARRAY3, int 维度 1, int 维度 2, int 维度 3 )
( int 维度 1, int 维度 2, int 维度 3, 数据类型* INPLACE_FARRAY3 )
4D:
(数据类型 INPLACE_ARRAY4[任意][任意][任意][任意])
(数据类型* INPLACE_ARRAY4, 维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, 维度类型 维度 4)
(维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, , 维度类型 维度 4, 数据类型* INPLACE_ARRAY4)
(数据类型* INPLACE_FARRAY4, 维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, 维度类型 维度 4)
(维度类型 维度 1, 维度类型 维度 2, 维度类型 维度 3, 维度类型 维度 4, 数据类型* INPLACE_FARRAY4)
这些类型映射现在会检查INPLACE_ARRAY
参数是否使用本机字节顺序。如果不是,则会引发异常。
还有一种“flat”就地数组,适用于无论维度如何都想修改或处理每个元素的情况。一个例子是“量化”函数,在此函数中,对数组的每个元素进行原地量化处理,无论是 1D、2D 还是其他。此形式会检查连续性,但允许 C 或 Fortran 排序。
ND:
(数据类型* INPLACE_ARRAY_FLAT, 维度类型 维度 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 中分配堆上的数组,并调用函数来填充数组的值的情况。在 Python 中,这些数组会为您分配并作为新数组对象返回。
注意,我们支持 1D 中的 DATA_TYPE*
argout 类型映射,但不支持 2D 或 3D。这是由于 SWIG 类型映射语法的一个怪癖,无法避免。请注意,对于这些类型的 1D 类型映射,Python 函数将接受表示 DIM1
的单个参数。
Argout 视图数组
Argoutview 数组用于在 C 代码中提供内部数据视图而不需要用户分配任何内存的情况。这可能是危险的。几乎没有办法保证 C 代码的内部数据在封装它的 NumPy 数组的整个生命周期内都保持存在。如果用户在销毁提供数据视图的对象之前销毁了 NumPy 数组,那么使用该数组可能导致错误的内存引用或分段错误。尽管如此,在处理大型数据集的情况下,有时您别无选择。
要包装 Argoutview 数组的 C 代码的特征是指针:指向维度和指向数据的双指针,以便这些值可以回传给用户。因此,argoutview 类型映射的签名为
1D:
(数据类型** 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 语法。因此,我们无法提供以下形式的类型映射:
代码语言:javascript复制%typemap(out) (TYPE[ANY]);
如果您遇到一个函数或方法返回指向数组的指针的情况,您最好的选择是编写自己的版本的函数来包装,可以是使用 %extend
对于类方法的情况,或使用 %ignore
和 %rename
对于函数的情况。
其他常见类型: bool
请注意,C 类型 bool
在可用类型映射部分的列表中不受支持。NumPy 布尔值是单个字节,而 C bool
是四个字节(至少在我的系统上是这样)。因此:
%numpy_typemaps(bool, NPY_BOOL, int)
会导致产生引用不正确数据长度的代码的 typemaps。你可以实现以下的宏展开:
代码语言:javascript复制%numpy_typemaps(bool, NPY_UINT, int)
为了解决数据长度的问题,输入数组会很好地运作,但是原地数组可能会失败类型检查。
其他常见类型:复杂
对于复杂浮点类型的 typemap 转换也不会自动支持。这是因为 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;
我们本可以实现的是:
代码语言:javascript复制%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
类型数组的 typemap。然而,不太可能会有任何独立(非 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 错误:
代码语言:javascript复制TypeError: in method 'MyClass_MyMethod', argument 2 of type 'int'
如果你传递的参数是从 NumPy 数组中提取的整数,并且你遇到了这个问题。解决方法是修改SWIG 类型转换系统,以接受 NumPy 数组标量,除了标准整数类型之外。幸运的是,这个功能已经为你提供。只需要复制文件:
代码语言:javascript复制pyfragments.swg
到项目的工作构建目录中,这个问题将会被解决。建议无论如何都这样做,因为这只会增加你的 Python 接口的功能。
为什么会有第二个文件?
SWIG的类型检查和转换系统是 C 宏、SWIG宏、SWIG类型映射和SWIG片段的复杂组合。片段是一种在需要时有条件地将代码插入包装文件中的方法,并且如果不需要则不插入。如果多个类型映射需要相同的片段,则该片段只会在包装代码中插入一次。
有一个用于将 Python 整数转换为 C long
的片段。还有一个将 Python 整数转换为 C int
的不同片段,并调用long
片段中定义的例程。我们可以通过更改long
片段的定义来实现所需的更改。SWIG使用“先到先得”系统确定片段的活动定义。也就是说,我们需要在SWIG内部执行之前定义long
转换的片段。通过将我们的片段定义放入文件pyfragments.swg
中,SWIG允许我们这样做。如果我们将新的片段定义放入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)a
的i
维度大小的值,假设a
可以被转换为PyArrayObject*
。 array_strides(a) 评估为类型为npy_intp
且长度为array_numdims(a)
的数组,给出a
的所有维度的步长,假设a
可以被转换为PyArrayObject*
。步长是元素与沿着相同轴的相邻元素之间的字节距离。 array_stride(a,i)a
的第i
个步长的值,假设a
可以被转换为PyArrayObject*
。 array_data(a) 评估为指向a
的数据缓冲区的void*
类型的指针,假设a
可以被转换为PyArrayObject*
。 array_descr(a) 返回对a
的 dtype 属性(PyArray_Descr*
)的借用引用,假定a
可转换为PyArrayObject*
。 array_flags(a) 返回表示a
的标志的整数,假定a
可以转换为PyArrayObject*
。 array_enableflags(a,f) 设置a
的标志(f
表示)的标志,假设a
可以转换为PyArrayObject*
。 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 整数类型代码。
返回一个描述与 NumPytypecode
对应的类型的字符串。
type_match()
返回类型:int
参数:
int actual_type
,NumPy 数组的 NumPy 类型代码。int desired_type
,期望的 NumPy 类型代码。
确保actual_type
与desired_type
兼容。例如,这允许字符和字节类型,或整数和长整数类型相匹配。现在等同于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
指令进行覆盖。
一个常见的例子
考虑一个合理的点积函数原型:
代码语言:javascript复制double dot(int len, double* vec1, double* vec2);
我们想要的 Python 接口是:
代码语言:javascript复制def dot(vec1, vec2):
"""
dot(PyObject,PyObject) -> double
"""
这里的问题是只有一个维度参数和两个数组参数,而且我们的类型映射是针对应用于单个数组的维度的(实际上,SWIG不提供一种将len
与接受两个 Python 输入参数的vec2
关联的机制)。推荐的解决方案如下:
%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
之前。或者,如果涉及的函数是一个类方法,除了%inline
之外,你还需要使用%extend
,而不是%ignore
。
关于错误处理的注解: 注意,my_dot
返回一个double
但它也可以引发 Python 错误。当向量长度不匹配时,得到的包装器函数将返回 Python 浮点表示为 0.0。由于这不是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 弃用后的情况如何。
介绍
简单包装器和接口生成器(或 SWIG)是一个强大的工具,用于为各种脚本语言生成包装器代码。SWIG可以解析头文件,并仅使用代码原型,为目标语言创建接口。但 SWIG 也不是无所不能的。例如,它无法从原型中知道:
代码语言:javascript复制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允许通过一种称为typemaps的机制来定义这些类型的转换。本文档提供了如何使用 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
定义的众多 typemap 之一在上面的代码中使用,其签名为(double* IN_ARRAY1, int DIM1)
。参数名称旨在建议 double*
参数是一个一维输入数组,而 int
表示该维度的大小。这恰好是 rms
原型中的模式。
很可能,没有实际的要包装的原型会具有参数名称 IN_ARRAY1
和 DIM1
。 我们使用 SWIG %apply
指令将一个维度为 double
的一维输入数组的类型映射应用到 rms
实际使用的原型上。 因此,有效地使用 numpy.i
需要知道可用的类型映射及其作用。
包括上述 SWIG 指令的 SWIG 接口文件将生成类似于以下内容的包装器代码:
代码语言:javascript复制 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 行,我们检查结果的有效性:它不为空,并且具有任意长度的单个维度。 一旦验证了这些状态,我们提取数据缓冲区和长度,以便我们可以在第 22 行调用底层 C 函数。 第 25 行对于我们创建了一个不再需要的新数组进行内存管理。
此代码有大量的错误处理。 请注意,SWIG_fail
是一个指向第 28 行标签的 goto fail
的宏。 如果用户提供了错误数量的参数,这将在第 10 行被捕获。 如果构造 NumPy 数组失败或产生了维度错误的数组,这些错误将在第 17 行被捕获。 最后,如果检测到错误,内存在第 30 行仍然被正确地管理。
注意,如果 C 函数签名的顺序不同:
代码语言:javascript复制double rms(int n, double* seq);
SWIG 不会将上述类型映射签名与 rms
的参数列表匹配。 幸运的是,numpy.i
具有一套具有数据指针的类型映射:最后 given last:
%apply (int DIM1, double* IN_ARRAY1) {(int n, double* seq)};
这简单地将上面生成的代码中第 3 和第 4 行的 arg1
和 arg2
的定义进行了交换,以及在第 19 和第 20 行的赋值。
使用 numpy.i
numpy.i
文件当前位于 numpy
安装目录下的 tools/swig
子目录中。通常,您会希望将其复制到您正在开发包装器的目录中。
只使用单个 SWIG 接口文件的简单模块应该包括以下内容:
代码语言:javascript复制%{
#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 排序(或 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)
这些 typemap 现在检查以确保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:
( 数据类型 ARGOUT_ARRAY1[任意] )
( DATA_TYPE* ARGOUT_ARRAY1, int DIM1 )
( int DIM1, DATA_TYPE* ARGOUT_ARRAY1 )
2D:
( DATA_TYPE ARGOUT_ARRAY2[任意][任意] )
3D:
( DATA_TYPE ARGOUT_ARRAY3[任意][任意][任意] )
4D:
( DATA_TYPE ARGOUT_ARRAY4[任意][任意][任意][任意] )
这些通常用于在 C/C 中分配一个或多个数组在堆上,并调用函数填充数组的值的情况。在 Python 中,这些数组会为您分配并作为新的数组对象返回。
请注意,我们支持 1D 中的 DATA_TYPE*
argout 类型映射,但不支持 2D 或 3D。这是因为 SWIG 类型映射语法的一个怪癖,无法避免。对于这些类型的 1D 类型映射,Python 函数将接受一个表示 DIM1
的单个参数。
Argout 视图数组
当您的 C 代码提供了对其内部数据的视图并且不需要用户分配任何内存时,argoutview 数组就派上了用场。这可能是危险的。几乎没有办法保证 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)
注意,不支持具有硬编码维度的数组。这些不能遵循这些类型映射的双指针签名。
内存管理的输出视图数组
numpy.i
的一个最新添加的功能是允许具有管理内存的输出数组的类型映射。请参见 此处的讨论。
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 语法。因此,我们无法提供以下形式的类型映射:
代码语言:javascript复制%typemap(out) (TYPE[ANY]);
如果你遇到一个函数或方法返回指向数组的指针的情况,你最好写一个自己的函数版本来进行包装,对于类方法可以使用 %extend
,对于函数可以使用 %ignore
和 %rename
。
其他常见类型:bool
注意,在 可用类型映射 部分,C 类型 bool
不受支持。NumPy 的布尔值占用一个字节,而 C 的 bool
占用四个字节(至少在我的系统上是这样)。因此:
%numpy_typemaps(bool, NPY_BOOL, int)
将导致 Typemaps 产生引用不正确数据长度的代码。您可以实现以下宏扩展:
代码语言:javascript复制%numpy_typemaps(bool, NPY_UINT, int)
来解决数据长度问题,输入数组 可能有效,但原位数组 可能无法通过类型检查。
其他常见类型:复数
复杂浮点类型的 Typemap 转换也不能自动支持。这是因为 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;
我们本来可以实现:
代码语言:javascript复制%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
扩展(用用户的复杂类型替换第一个参数)应该有效。
输入数组
输入数组被定义为传入程序但不会被直接更改或返回给用户的数据数组。因此,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] )
是给定固定维度的二维数组,类似地适用于三维数组。
原位数组
In-place arrays are defined as arrays that are modified in-place. The input values may or may not be used, but the values at the time the function returns are significant. The provided Python argument must therefore be a NumPy array of the required type. The in-place signatures are
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)
These typemaps now check to make sure that the INPLACE_ARRAY
arguments use native byte ordering. If not, an exception is raised.
There is also a “flat” in-place array for situations in which you would like to modify or process each element, regardless of the number of dimensions. One example is a “quantization” function that quantizes each element of an array in-place, be it 1D, 2D or whatever. This form checks for continuity but allows either C or Fortran ordering.
ND:
(DATA_TYPE* INPLACE_ARRAY_FLAT, DIM_TYPE DIM_FLAT)
Argout Arrays
Argout arrays are arrays that appear in the input arguments in C, but are in fact output arrays. This pattern occurs often when there is more than one output variable and the single return argument is therefore not sufficient. In Python, the conventional way to return multiple arguments is to pack them into a sequence (tuple, list, etc.) and return the sequence. This is what the argout typemaps do. If a wrapped function that uses these argout typemaps has more than one return argument, they are packed into a tuple or list, depending on the version of Python. The Python user does not pass these arrays in, they simply get returned. For the case where a dimension is specified, the python user must provide that dimension as an argument. The argout signatures are
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 typemaps 在 1D 中,但不支持 2D 或 3D。 这是由于SWIG typemap 语法的一个怪癖,无法避免。 请注意,对于这些类型的 1D typemaps,Python 函数将采用一个表示DIM1
的单个参数。
输出视图数组
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)
请注意,不支持具有硬编码尺寸的数组。 这些无法遵循这些 typemaps 的双指针签名。
内存管理的 Argout 视图数组
numpy.i
最近新增了允许管理视图的 argout 数组的 typemaps。请查看这里的讨论。
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
接口文件不支持用于输出数组的 typemaps,原因有几点。首先,C/C 返回参数限制为单个值。这阻止了以一般方式获取维度信息。其次,不允许作为返回参数的硬编码长度的数组。换句话说:
double[3] newVector(double x, double y, double z);
不是合法的 C/C 语法。因此,我们不能提供以下形式的 typemaps:
代码语言:javascript复制%typemap(out) (TYPE[ANY]);
如果遇到函数或方法返回指向数组的指针的情况,最好编写要包装的函数的自定义版本,对于类方法的情况,可以使用%extend
,对于函数的情况,可以使用%ignore
和%rename
。
其他常见类型:布尔值
注意:C 类型bool
在可用类型映射部分不受支持。 NumPy 的布尔值占用一个字节,而 C 的bool
占用四个字节(至少在我的系统上是这样)。因此:
%numpy_typemaps(bool, NPY_BOOL, int)
会产生引用错误数据长度的 typemaps。您可以实现以下宏扩展:
代码语言:javascript复制%numpy_typemaps(bool, NPY_UINT, int)
为了解决数据长度问题,输入数组将正常工作,但原位数组可能会失败类型检查。
其他常见类型:复数
对于复杂浮点类型的 typemap 转换也不会自动支持。这是因为 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;
我们本来可以实现:
代码语言:javascript复制%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 数组中提取整数,并尝试将其传递给一个期望int
的 SWIG 包装的 C/C 函数,但是 SWIG 的类型检查不会将 NumPy 数组标量识别为整数。(通常情况下,这实际上是有效的-这取决于 NumPy 是否识别你所用的整数类型作为继承于你所用平台上的 Python 整数类型。有时,这意味着在 32 位机器上工作的代码在 64 位机器上会失败。)
如果你遇到一个看起来像下面的 Python 错误:
代码语言:javascript复制TypeError: in method 'MyClass_MyMethod', argument 2 of type 'int'
并且你传递的参数是从 NumPy 数组中提取的整数,那么你就遇到了这个问题。解决方法是修改 SWIG 的类型转换系统以接受 NumPy 数组标量,除了标准整数类型之外。幸运的是,这个功能已经为你提供了。只需复制文件:
代码语言:javascript复制pyfragments.swg
到你的项目工作构建目录,并且这个问题就会被解决。建议你无论如何都这样做,因为它只会增加你的 Python 接口的功能。
为什么还有第二个文件?
SWIG的类型检查和转换系统是复杂的 C 宏、SWIG 宏、SWIG 类型映射和 SWIG 片段的复杂组合。片段是一种在需要时有条件地将代码插入到包装文件中的方法,如果不需要,则不插入。如果多个类型映射需要同一个片段,那么片段只会被插入你的包装代码一次。
有一个片段用于将 Python 整数转换为 C 的long
。有另一个片段将 Python 整数转换为 C 的int
,并调用在long
片段中定义的例程。我们可以通过更改long
片段的定义在这里做我们想要的更改。SWIG使用“先到先服务”系统来确定片段的活动定义。也就是说,我们需要在SWIG内部执行之前为long
转换定义片段。SWIG允许我们通过将我们的片段定义放在文件pyfragments.swg
中来实现这一点。如果我们将新的片段定义放在numpy.i
中,它们将被忽略。
为什么有第二个文件?
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
的整数数据类型代码求值,假设a
可以转换为PyArrayObject*
。 array_numdims(a) 对a
的维数求值,假设a
可以转换为PyArrayObject*
。 array_dimensions(a) 评估为类型为npy_intp
且长度为array_numdims(a)
的数组,给出a
的所有维度的长度,假设a
可以转换为PyArrayObject*
。 array_size(a,i) 评估为a
的第i
个维度大小,假设a
可以转换为PyArrayObject*
。 array_strides(a) 评估为类型为npy_intp
且长度为array_numdims(a)
的数组,给出a
的所有维度的跨度,假设a
可以转换为PyArrayObject*
。跨度是元素与沿同一轴的相邻元素之间的字节距离。 array_stride(a,i) 评估为a
的第i
个跨度,假设a
可以转换为PyArrayObject*
。 array_data(a) 评估为指向a
的数据缓冲区的void*
类型的指针,假设a
可以转换为PyArrayObject*
。 array_descr(a) 返回对a
的 dtype 属性(PyArray_Descr*
)的借用引用,假设a
可以转换为PyArrayObject*
。 array_flags(a) 返回一个整数,表示a
的标志,假设a
可以转换为PyArrayObject*
。 array_enableflags(a,f) 设置a
的标志f
,假设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 排序标志并重新计算步幅。
宏
is_array(a) 如果
a
是非NULL
并且可以转换为PyArrayObject*
,则评估为真。 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
的 dtype 属性(PyArray_Descr*
)的借用引用,假设可以将a
转换为PyArrayObject*
。 array_flags(a) 返回表示a
的标志的整数,假设可以将a
转换为PyArrayObject*
。 array_enableflags(a,f) 设置代表a
的f
标志,假设可以将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
兼容。例如,这允许字符和字节类型,或整数和长整数类型匹配。这现在等同于 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
指令。
一个常见的例子
考虑一个合理的点积函数原型:
代码语言:javascript复制double dot(int len, double* vec1, double* vec2);
我们想要的 Python 接口是:
代码语言:javascript复制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
前面放置。或者,如果问题函数是类方法,则您将需要在%inline
之外使用%extend
而不是使用%ignore
指令。
**关于错误处理的注意事项:**请注意,my_dot
返回一个 double
值,但它也可能引发 Python 错误。当向量长度不匹配时,生成的包装函数将返回 Python 中的浮点表示 0.0。因为这不是 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);
一般来说,您应该特别指定这些类型映射签名的目标位置,然后在完成后将其清除。
一个常见示例
考虑一个合理的点积函数原型:
代码语言:javascript复制double dot(int len, double* vec1, double* vec2);
我们希望得到的 Python 接口是:
代码语言:javascript复制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;
指令。或者,如果涉及的函数是类方法,则除了使用%inline
外,还需要使用%extend
而不是%ignore
。
**关于错误处理的说明:**请注意,my_dot
返回一个double
,但也可能引发 Python 错误。结果包装函数将在向量长度不匹配时返回 Python 浮点数表示的 0.0。由于这不是NULL
,Python 解释器将不知道要检查错误。因此,我们在my_dot
上方添加了%exception
指令,以获取我们想要的行为(请注意$action
是一个宏,展开为对my_dot
的有效调用)。一般情况下,您可能需要编写SWIG宏来执行此任务。
其他情况
在其他封装情况下,当遇到时,可能需要使用numpy.i
。
在某些情况下,您可以使用%numpy_typemaps
宏为自己的类型实现类型映射。查看其他常见类型:bool 或其他常见类型:复数部分获取示例。另一种情况是,如果您的维度不是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
,这些 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
默认提供支持在 NumPy 数组和 C 数组之间转换的 typemaps:
- 这可以是 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
),有三个参数,用于为用户选择的 C 数据类型、NumPy 数据类型(假设它们匹配)和维度类型实现 74 个参数签名。 - 十四个 C 宏和十五个 C 函数,可用于编写处理提供的 typemaps 未涵盖情况的专用 typemap、扩展或内联函数。请注意,这些宏和函数专门编码以与 NumPy C/API 一起使用,而不管 NumPy 版本号如何,包括在版本 1.6 之后一些 API 的弃用后。