2.8.4 SWIG
SWIG, 简化封装接口生成器,是一个联接用C和C++写的程序与需要高级程序语言,包括Python的软件开发工具。SWIG的重点在于它可以为你自动生成封装器代码。尽管从编程时间上来说这是一个优势,但是同时也是一个负担。生成的文件通常很大,并且可能并不是人类可读的,封装过程造成的多层间接引用可能很难理解。
注意 自动生成的C代码使用Python-C-Api。
优势
- 给定头部可以自动封装整个库
- 在C++中表现良好
不足
- 自动生成的文件很庞大
- 如果出错很难debug
- 陡峭的学习曲线
2.8.4.1 例子
让我们想象我们的cos函数存在于用C写的cos_module中,包含在源文件cos_module.c中:
In [ ]:
#include <math.h>double cos_func(double arg){return cos(arg);}
头文件cos_module.h:
In [ ]:
double cos_func(double arg);
尽管我们的目的是将cos_func暴露给Python。要用SWIG来完成这个目的,我们需要写一个包含SWIG指导的接口文件。
In [ ]:
/* Example of wrapping cos function from math.h using SWIG. */%module cos_module%{/* the resulting C file should be built as a python extension */#define SWIG_FILE_WITH_INIT/* Includes the header in the wrapper code */#include "cos_module.h"%}/* Parse the header file to generate wrappers */%include "cos_module.h"
如你所见,这里不需要太多的代码。对于这个简单的例子,它简单到只需要在接口文件中包含一个头文件,来向Python暴露函数。但是,SWIG确实允许更多精细包含或者排除在头文件中发现的函数,细节检查一下文档。
生成编译的封装器是一个两阶段的过程:
在接口文件上运行
swig可执行文件来生成文件cos_module_wrap.c, 其源文件是自动生成的Python C-extension和cos_module.py, 是自动生成的Python模块。编译
cos_module_wrap.c到_cos_module.so。幸运的,distutils知道如何处理SWIG接口文件, 因此我们的setup.py是很简单的:
In [ ]:
from distutils.core import setup, Extensionsetup(ext_modules=[Extension("_cos_module",sources=["cos_module.c", "cos_module.i"])])
In [ ]:
$ cd advanced/interfacing_with_c/swig$ lscos_module.c cos_module.h cos_module.i setup.py$ python setup.py build_ext --inplacerunning build_extbuilding '_cos_module' extensionswigging cos_module.i to cos_module_wrap.cswig -python -o cos_module_wrap.c cos_module.icreating buildcreating build/temp.linux-x86_64-2.7gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.ogcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module_wrap.c -o build/temp.linux-x86_64-2.7/cos_module_wrap.ogcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o build/temp.linux-x86_64-2.7/cos_module_wrap.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/_cos_module.so$ lsbuild/ cos_module.c cos_module.h cos_module.i cos_module.py _cos_module.so* cos_module_wrap.c setup.py
我们可以像前面的例子中那样加载和运行cos_module:
In [ ]:
In [1]: import cos_moduleIn [2]: cos_module?Type: moduleString Form:<module 'cos_module' from 'cos_module.py'>File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/cos_module.pyDocstring: <no docstring>In [3]: dir(cos_module)Out[3]:['__builtins__','__doc__','__file__','__name__','__package__','_cos_module','_newclass','_object','_swig_getattr','_swig_property','_swig_repr','_swig_setattr','_swig_setattr_nondynamic','cos_func']In [4]: cos_module.cos_func(1.0)Out[4]: 0.5403023058681398In [5]: cos_module.cos_func(0.0)Out[5]: 1.0In [6]: cos_module.cos_func(3.14159265359)Out[6]: -1.0
接下来我们测试一下强壮性,我们看到我们可以获得一个更多的错误信息 (虽然, 严格来讲在Python中没有double类型):
In [ ]:
In [7]: cos_module.cos_func('foo')------------- ------------- ------------- ------------- -----------------------TypeError Traceback (most recent call last)<ipython-input-7-11bee483665d> in <module>()----> 1 cos_module.cos_func('foo')TypeError: in method 'cos_func', argument 1 of type 'double'
2.8.4.2 Numpy 支持
Numpy在numpy.i文件中提供了SWIG的支持。这个接口文件定义了许多所谓的typemaps,支持了Numpy数组和C-Arrays的转化。在接下来的例子中,我们将快速看一下typemaps实际是如何工作的。
我们有相同的cos_doubles函数,在ctypes例子中:
In [ ]:
void cos_doubles(double * in_array, double * out_array, int size);
In [ ]:
#include <math.h>/* Compute the cosine of each element in in_array, storing the result in* out_array. */void cos_doubles(double * in_array, double * out_array, int size){int i;for(i=0;i<size;i++){out_array[i] = cos(in_array[i]);}}
使用了SWIG接口文件封装了cos_doubles_func:
In [ ]:
/* Example of wrapping a C function that takes a C double array as input using* numpy typemaps for SWIG. */%module cos_doubles%{/* the resulting C file should be built as a python extension */#define SWIG_FILE_WITH_INIT/* Includes the header in the wrapper code */#include "cos_doubles.h"%}/* include the numpy typemaps */%include "numpy.i"/* need this for correct module initialization */%init %{import_array();%}/* typemaps for the two arrays, the second will be modified in-place */%apply (double* IN_ARRAY1, int DIM1) {(double * in_array, int size_in)}%apply (double* INPLACE_ARRAY1, int DIM1) {(double * out_array, int size_out)}/* Wrapper for cos_doubles that massages the types */%inline %{/* takes as input two numpy arrays */void cos_doubles_func(double * in_array, int size_in, double * out_array, int size_out) {/* calls the original funcion, providing only the size of the first */cos_doubles(in_array, out_array, size_in);}%}
- 要使用Numpy的typemaps, 我们需要包含
numpy.i文件。 - 观察一下对
import_array()的调用,这个模块我们已经在Numpy-C-API例子中遇到过。 - 因为类型映射只支持ARRAY、SIZE的签名,我们需要将cos_doubles封装为cos_doubles_func,接收两个数组包括大小作为输入。
- 与SWIG不同的是, 我们并没有包含
cos_doubles.h头部,我们并不需要暴露给Python,因为,我们通过cos_doubles_func暴露了相关的功能。
并且,和之前一样,我们可以用distutils来封装这个函数:
In [ ]:
from distutils.core import setup, Extensionimport numpysetup(ext_modules=[Extension("_cos_doubles",sources=["cos_doubles.c", "cos_doubles.i"],include_dirs=[numpy.get_include()])])
和前面一样,我们需要用include_dirs来制定位置。
In [ ]:
$ lscos_doubles.c cos_doubles.h cos_doubles.i numpy.i setup.py test_cos_doubles.py$ python setup.py build_ext -irunning build_extbuilding '_cos_doubles' extensionswigging cos_doubles.i to cos_doubles_wrap.cswig -python -o cos_doubles_wrap.c cos_doubles.icos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found.cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found.cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found.creating buildcreating build/temp.linux-x86_64-2.7gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles.c -o build/temp.linux-x86_64-2.7/cos_doubles.ogcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles_wrap.c -o build/temp.linux-x86_64-2.7/cos_doubles_wrap.oIn file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722,from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17,from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15,from cos_doubles_wrap.c:2706:/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/npy_deprecated_api.h:11:2: warning: #warning "Using deprecated NumPy API, disable it by #defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION"gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_doubles.o build/temp.linux-x86_64-2.7/cos_doubles_wrap.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig_numpy/_cos_doubles.so$ lsbuild/ cos_doubles.h cos_doubles.py cos_doubles_wrap.c setup.pycos_doubles.c cos_doubles.i _cos_doubles.so* numpy.i test_cos_doubles.py
并且,和前面一样,我们来验证一下它工作正常:
In [ ]:
import numpy as npimport pylabimport cos_doublesx = np.arange(0, 2 * np.pi, 0.1)y = np.empty_like(x)cos_doubles.cos_doubles_func(x, y)pylab.plot(x, y)pylab.show()
