SWIG Numpy 示例¶
日期 | 2009-08-11(最后修改),2008-12-31(创建) |
---|
简介¶
这些是使用 numpy.i 接口文件的简单 !NumPy 和 SWIG 示例。对于希望在 Win32 环境中使用这些示例的人员,还提供了一个 MinGW 部分。以下代码是 C 语言,而不是 C++。
此处包含的信息最初由 Bill Spotz 在其文章numpy.i: a SWIG Interface File for !NumPy中提供,以及可以通过以下命令签出的 !NumPy SVN
svn co http://scipy.org/svn/numpy/trunk numpy
swig -version
和
gcc -v
两者都应该输出一些文本...
修改 pyfragments.swg 文件(仅限 MinGW)¶
这是我自己的测试结果,运行 SWIG 版本 1.3.36 和 gcc 版本 3.4.5(mingw-vista 特别版 r3)。我必须从源代码中删除“static”语句,否则你的 SWIGed 源代码将无法编译。该文件中只有两个“static”语句,都需要删除。这是我修改后的版本:pyfragments.swg
编译和测试¶
首先需要为每个模块编写一个特定的 setup.py 文件。我参考了 https://scipy.org.cn/svn/numpy/trunk/doc/swig/test/ 中的参考 setup.py 文件,并添加了自动处理 swig 的功能。
在类 Unix 系统上,命令行是
python setup.py build
在 Win32 环境(cygwin 或 cmd)中,设置命令行是(用于 MinGW)
python setup.py build --compiler=mingw32
该命令同时处理 SWIG 过程(生成包装 C 和 Python 代码)和 gcc 编译。生成的模块(一个 pyd 文件)将构建在 `build\lib.XXX` 目录中(例如,对于 Python 2.5 安装和 Win32 机器,则为 `build\lib.win32-2.5` 目录)。
一个简单的 ARGOUT_ARRAY1 示例¶
这是一个 range 函数的重新实现。该模块称为 ezrange。使用 `ARGOUT_ARRAY1` 时要记住的一件事是,必须从 Python 传递数组的维度。
来自 Bill Spotz 的文章:Python 用户不会传入这些数组,它们只是被返回。对于指定维度的案例,Python 用户必须提供该维度作为参数。
这对于像 `numpy.arange(N)` 这样的函数很有用,因为返回数组的大小事先已知并传递给 C 函数。
对于遵循 `array_out = function(array_in)` 的函数,其中 array_out 的大小事先未知,并且取决于在 C 中分配的内存,请参见 [:Cookbook/SWIG Memory Deallocation] 中给出的示例。
C 源代码(ezrange.c 和 ezrange.h)¶
这是 ezrange.h 文件
void range(int *rangevec, int n);
这是 ezrange.c 文件
void range(int *rangevec, int n)
{
int i;
for (i=0; i< n; i++)
rangevec[i] = i;
}
%module ezrange
%{
#define SWIG_FILE_WITH_INIT
#include "ezrange.h"
%}
%include "numpy.i"
%init %{
import_array();
%}
%apply (int* ARGOUT_ARRAY1, int DIM1) {(int* rangevec, int n)}
%include "ezrange.h"
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# ezrange extension module
_ezrange = Extension("_ezrange",
["ezrange.i","ezrange.c"],
include_dirs = [numpy_include],
)
# ezrange setup
setup( name = "range function",
description = "range takes an integer and returns an n element int array where each element is equal to its index",
author = "Egor Zindy",
version = "1.0",
ext_modules = [_ezrange]
)
编译模块¶
设置命令行是
python setup.py build
或者
python setup.py build --compiler=mingw32
取决于您的环境。
测试模块¶
如果一切按计划进行,应该在 `build\lib.XXX` 目录中有一个 `_ezrange.pyd` 文件。您需要将该文件复制到 `ezrange.py` 文件所在的目录(由 swig 生成),在这种情况下,以下操作将起作用(在 python 中)
>>> import ezrange
>>> ezrange.range(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
void inplace(double *invec, int n);
这是 inplace.c 文件
void inplace(double *invec, int n)
{
int i;
for (i=0; i<n; i++)
{
invec[i] = 2*invec[i];
}
}
%module inplace
%{
#define SWIG_FILE_WITH_INIT
#include "inplace.h"
%}
%include "numpy.i"
%init %{
import_array();
%}
%apply (double* INPLACE_ARRAY1, int DIM1) {(double* invec, int n)}
%include "inplace.h"
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# inplace extension module
_inplace = Extension("_inplace",
["inplace.i","inplace.c"],
include_dirs = [numpy_include],
)
# NumyTypemapTests setup
setup( name = "inplace function",
description = "inplace takes a double array and doubles each of its elements in-place.",
author = "Egor Zindy",
version = "1.0",
ext_modules = [_inplace]
)
编译模块¶
设置命令行是
python setup.py build
或者
python setup.py build --compiler=mingw32
取决于您的环境。
测试模块¶
如果一切按计划进行,应该在 `build\lib.XXX` 目录中有一个 `_inplace.pyd` 文件。您需要将该文件复制到 `inplace.py` 文件所在的目录(由 swig 生成),在这种情况下,以下操作将起作用(在 python 中)
>>> import numpy
>>> import inplace
>>> a = numpy.array([1,2,3],'d')
>>> inplace.inplace(a)
>>> a
array([2., 4., 6.])
一个简单的 ARGOUTVIEW_ARRAY1 示例¶
大量警告¶
请注意,Bill Spotz 建议不要使用 argout_view 数组,除非绝对必要
Argoutview 数组用于当您的 C 代码为您提供其内部数据的视图时,并且不需要用户分配任何内存。这可能很危险。几乎无法保证来自 C 代码的内部数据在封装它的 !NumPy 数组的整个生命周期内都将存在。如果用户在销毁 !NumPy 数组之前销毁了提供数据视图的对象,那么使用该数组可能会导致内存引用错误或段错误。尽管如此,在处理大型数据集时,您别无选择。
正如 Travis Oliphant 在这里所说,Python 不负责内存释放:1
然而,棘手的部分是内存管理。内存是如何释放的?建议一直都是类似于“确保内存不会在 !NumPy 数组消失之前被释放”。这是一个很好的建议,但通常没有帮助,因为它基本上只是告诉您创建一个内存泄漏。
内存释放也很难自动处理,因为没有简单的方法来进行模块“终结”。有一个 `Py_InitModule()` 函数,但没有用于处理删除/销毁/终结的函数(这将在 Python 3000 中得到解决,如 PEP3121 中所述)。在我的示例中,我使用 python 模块 atexit,但一定有更好的方法。
话虽如此,如果你别无选择,这里有一个使用 ARGOUTVIEW_ARRAY1 的示例。和往常一样,欢迎评论!
该模块声明了一块内存和几个函数:* ezview.set_ones() 将内存块中的所有元素(双精度浮点数)设置为 1,并返回一个指向该内存块的视图的 numpy 数组。* ezview.get_view() 只是返回内存块的视图。* ezview.finalize() 负责内存释放(这是此示例的薄弱环节)。
C 源代码 (ezview.c 和 ezview.h)¶
这是 ezview.h 文件
void set_ones(double *array, int n);
这是 ezview.c 文件
#include <stdio.h>
#include <stdlib.h>
#include "ezview.h"
void set_ones(double *array, int n)
{
int i;
if (array == NULL)
return;
for (i=0;i<n;i++)
array[i] = 1.;
}
%module ezview
%{
#define SWIG_FILE_WITH_INIT
#include "ezview.h"
double *my_array = NULL;
int my_n = 10;
void __call_at_begining()
{
printf("__call_at_begining...\n");
my_array = (double *)malloc(my_n*sizeof(double));
}
void __call_at_end(void)
{
printf("__call_at_end...\n");
if (my_array != NULL)
free(my_array);
}
%}
%include "numpy.i"
%init %{
import_array();
__call_at_begining();
%}
%apply (double** ARGOUTVIEW_ARRAY1, int *DIM1) {(double** vec, int* n)}
%include "ezview.h"
%rename (set_ones) my_set_ones;
%inline %{
void finalize(void){
__call_at_end();
}
void get_view(double **vec, int* n) {
*vec = my_array;
*n = my_n;
}
void my_set_ones(double **vec, int* n) {
set_ones(my_array,my_n);
*vec = my_array;
*n = my_n;
}
%}
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# view extension module
_ezview = Extension("_ezview",
["ezview.i","ezview.c"],
include_dirs = [numpy_include],
)
# NumyTypemapTests setup
setup( name = "ezview module",
description = "ezview provides 3 functions: set_ones(), get_view() and finalize(). set_ones() and get_view() provide a view on a memory block allocated in C, finalize() takes care of the memory deallocation.",
author = "Egor Zindy",
version = "1.0",
ext_modules = [_ezview]
)
编译模块¶
设置命令行是
python setup.py build
或者
python setup.py build --compiler=mingw32
取决于您的环境。
测试模块¶
如果一切按计划进行,应该在 `build\lib.XXX` 目录中有一个 `_ezview.pyd` 文件。你需要将该文件复制到 `ezview.py` 文件所在的目录(由 swig 生成),在这种情况下,以下操作将在 python 中起作用
测试代码 test_ezview.py 如下
import atexit
import numpy
print "first message is from __call_at_begining()"
import ezview
#There is no easy way to finalize the module (see PEP3121)
atexit.register(ezview.finalize)
a = ezview.set_ones()
print "\ncalling ezview.set_ones() - now the memory block is all ones.\nReturned array (a view on the allocated memory block) is:"
print a
print "\nwe're setting the array using a[:]=arange(a.shape[0])\nThis changes the content of the allocated memory block:"
a[:] = numpy.arange(a.shape[0])
print a
print "\nwe're now deleting the array - this only deletes the view,\nnot the allocated memory!"
del a
print "\nlet's get a new view on the allocated memory, should STILL contain [0,1,2,3...]"
b = ezview.get_view()
print b
print "\nnext message from __call_at_end() - finalize() registered via module atexit"
启动 test_ezview.py,希望会发生以下情况
~> python test_ezview.py
first message is from __call_at_begining()
__call_at_begining...
calling ezview.set_ones() - now the memory block is all ones.
Returned array (a view on the allocated memory block) is:
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
we re setting the array using a[:]=arange(a.shape[0])
This changes the content of the allocated memory block:
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
we re now deleting the array - this only deletes the view,
not the allocated memory!
let s get a new view on the allocated memory, should STILL contain [0,1,2,3...]
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
next message from __call_at_end() - finalize() registered via module atexit
__call_at_end...
使用 errno 和 python 异常进行错误处理¶
我已经测试了几个月,这是我想到的最好的方法。如果有人知道更好的方法,请告诉我。
从 opengroup 网站上看,左值 errno 被许多函数用来返回错误值。其思想是在调用函数之前将全局变量 errno 设置为 0(在 swig 语法中:\$action),并在之后检查。如果 errno 非零,则会根据 errno 的值生成带有有意义消息的 python 异常。
以下示例包含两个示例:第一个示例在检查数组索引是否有效时使用 errno。第二个示例使用 errno 通知用户 malloc() 问题。
C 源代码 (ezerr.c 和 ezerr.h)¶
这是 ezerr.h 文件
int val(int *array, int n, int index);
void alloc(int n);
这是 ezerr.c 文件
#include <stdlib.h>
#include <errno.h>
#include "ezerr.h"
//return the array element defined by index
int val(int *array, int n, int index)
{
int value=0;
if (index < 0 || index >=n)
{
errno = EPERM;
goto end;
}
value = array[index];
end:
return value;
}
//allocate (and free) a char array of size n
void alloc(int n)
{
char *array;
array = (char *)malloc(n*sizeof(char));
if (array == NULL)
{
errno = ENOMEM;
goto end;
}
//don't keep the memory allocated...
free(array);
end:
return;
}
%module ezerr
%{
#include <errno.h>
#include "ezerr.h"
#define SWIG_FILE_WITH_INIT
%}
%include "numpy.i"
%init %{
import_array();
%}
%exception
{
errno = 0;
$action
if (errno != 0)
{
switch(errno)
{
case EPERM:
PyErr_Format(PyExc_IndexError, "Index out of range");
break;
case ENOMEM:
PyErr_Format(PyExc_MemoryError, "Failed malloc()");
break;
default:
PyErr_Format(PyExc_Exception, "Unknown exception");
}
SWIG_fail;
}
}
%apply (int* IN_ARRAY1, int DIM1) {(int *array, int n)}
%include "ezerr.h"
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# err extension module
ezerr = Extension("_ezerr",
["ezerr.i","ezerr.c"],
include_dirs = [numpy_include],
extra_compile_args = ["--verbose"]
)
# NumyTypemapTests setup
setup( name = "err test",
description = "A simple test to demonstrate the use of errno and python exceptions",
author = "Egor Zindy",
version = "1.0",
ext_modules = [ezerr]
)
编译模块¶
设置命令行是
python setup.py build
或者
python setup.py build --compiler=mingw32
取决于您的环境。
测试模块¶
如果一切按计划进行,在`build\lib.XXX`目录中应该有一个`_ezerr.pyd`文件。您需要将该文件复制到`ezerr.py`文件所在的目录(由swig生成),在这种情况下,以下操作将起作用(在python中)
测试代码test_err.py如下
import traceback,sys
import numpy
import ezerr
print "\n--- testing ezerr.val() ---"
a = numpy.arange(10)
indexes = [5,20,-1]
for i in indexes:
try:
value = ezerr.val(a,i)
except:
print ">> failed for index=%d" % i
traceback.print_exc(file=sys.stdout)
else:
print "using ezerr.val() a[%d]=%d - should be %d" % (i,value,a[i])
print "\n--- testing ezerr.alloc() ---"
amounts = [1,-1] #1 byte, -1 byte
for n in amounts:
try:
ezerr.alloc(n)
except:
print ">> could not allocate %d bytes" % n
traceback.print_exc(file=sys.stdout)
else:
print "allocated (and deallocated) %d bytes" % n
启动test_err.py,以下操作将有望发生
~> python test_err.py
--- testing ezerr.val() ---
using ezerr.val() a[5]=5 - should be 5
>> failed for index=20
Traceback (most recent call last):
File "test_err.py", line 11, in <module>
value = ezerr.val(a,i)
IndexError: Index out of range
>> failed for index=-1
Traceback (most recent call last):
File "test_err.py", line 11, in <module>
value = ezerr.val(a,i)
IndexError: Index out of range
--- testing ezerr.alloc() ---
allocated (and deallocated) 1 bytes
>> could not allocate -1 bytes
Traceback (most recent call last):
File "test_err.py", line 23, in <module>
ezerr.alloc(n)
MemoryError: Failed malloc()
double dot(int len, double* vec1, double* vec2);
这是dot.c文件
#include <stdio.h>
#include "dot.h"
double dot(int len, double* vec1, double* vec2)
{
int i;
double d;
d = 0;
for(i=0;i<len;i++)
d += vec1[i]*vec2[i];
return d;
}
%module dot
%{
#define SWIG_FILE_WITH_INIT
#include "dot.h"
%}
%include "numpy.i"
%init %{
import_array();
%}
%apply (int DIM1, double* IN_ARRAY1) {(int len1, double* vec1), (int len2, double* vec2)}
%include "dot.h"
%rename (dot) my_dot;
%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);
}
%}
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# dot extension module
_dot = Extension("_dot",
["dot.i","dot.c"],
include_dirs = [numpy_include],
)
# dot setup
setup( name = "Dot product",
description = "Function that performs a dot product (numpy.i: a SWIG Interface File for NumPy)",
author = "Egor Zindy (based on the setup.py file available in the numpy tree)",
version = "1.0",
ext_modules = [_dot]
)
编译模块¶
设置命令行是
python setup.py build
或者
python setup.py build --compiler=mingw32
取决于您的环境。
测试¶
如果一切按计划进行,在`build\lib.XXX`目录中应该有一个`_dot.pyd`文件。您需要将该文件复制到`dot.py`文件所在的目录(由swig生成),在这种情况下,以下操作将起作用(在python中)
>>> import dot
>>> dot.dot([1,2,3],[1,2,3])
14.0
章节作者:EgorZindy