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 [ ]:

  1. #include <math.h>
  2. double cos_func(double arg){
  3. return cos(arg);
  4. }

头文件cos_module.h:

In [ ]:

  1. double cos_func(double arg);

尽管我们的目的是将cos_func暴露给Python。要用SWIG来完成这个目的,我们需要写一个包含SWIG指导的接口文件。

In [ ]:

  1. /* Example of wrapping cos function from math.h using SWIG. */
  2. %module cos_module
  3. %{
  4. /* the resulting C file should be built as a python extension */
  5. #define SWIG_FILE_WITH_INIT
  6. /* Includes the header in the wrapper code */
  7. #include "cos_module.h"
  8. %}
  9. /* Parse the header file to generate wrappers */
  10. %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 [ ]:

  1. from distutils.core import setup, Extension
  2. setup(ext_modules=[Extension("_cos_module",
  3. sources=["cos_module.c", "cos_module.i"])])

In [ ]:

  1. $ cd advanced/interfacing_with_c/swig
  2. $ ls
  3. cos_module.c cos_module.h cos_module.i setup.py
  4. $ python setup.py build_ext --inplace
  5. running build_ext
  6. building '_cos_module' extension
  7. swigging cos_module.i to cos_module_wrap.c
  8. swig -python -o cos_module_wrap.c cos_module.i
  9. creating build
  10. creating build/temp.linux-x86_64-2.7
  11. gcc -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.o
  12. gcc -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.o
  13. gcc -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
  14. $ ls
  15. build/ cos_module.c cos_module.h cos_module.i cos_module.py _cos_module.so* cos_module_wrap.c setup.py

我们可以像前面的例子中那样加载和运行cos_module:

In [ ]:

  1. In [1]: import cos_module
  2. In [2]: cos_module?
  3. Type: module
  4. String Form:<module 'cos_module' from 'cos_module.py'>
  5. File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/cos_module.py
  6. Docstring: <no docstring>
  7. In [3]: dir(cos_module)
  8. Out[3]:
  9. ['__builtins__',
  10. '__doc__',
  11. '__file__',
  12. '__name__',
  13. '__package__',
  14. '_cos_module',
  15. '_newclass',
  16. '_object',
  17. '_swig_getattr',
  18. '_swig_property',
  19. '_swig_repr',
  20. '_swig_setattr',
  21. '_swig_setattr_nondynamic',
  22. 'cos_func']
  23. In [4]: cos_module.cos_func(1.0)
  24. Out[4]: 0.5403023058681398
  25. In [5]: cos_module.cos_func(0.0)
  26. Out[5]: 1.0
  27. In [6]: cos_module.cos_func(3.14159265359)
  28. Out[6]: -1.0

接下来我们测试一下强壮性,我们看到我们可以获得一个更多的错误信息 (虽然, 严格来讲在Python中没有double类型):

In [ ]:

  1. In [7]: cos_module.cos_func('foo')
  2.  ------------- ------------- ------------- ------------- -----------------------
  3. TypeError Traceback (most recent call last)
  4. <ipython-input-7-11bee483665d> in <module>()
  5. ----> 1 cos_module.cos_func('foo')
  6. 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 [ ]:

  1. void cos_doubles(double * in_array, double * out_array, int size);

In [ ]:

  1. #include <math.h>
  2. /* Compute the cosine of each element in in_array, storing the result in
  3. * out_array. */
  4. void cos_doubles(double * in_array, double * out_array, int size){
  5. int i;
  6. for(i=0;i<size;i++){
  7. out_array[i] = cos(in_array[i]);
  8. }
  9. }

使用了SWIG接口文件封装了cos_doubles_func:

In [ ]:

  1. /* Example of wrapping a C function that takes a C double array as input using
  2. * numpy typemaps for SWIG. */
  3. %module cos_doubles
  4. %{
  5. /* the resulting C file should be built as a python extension */
  6. #define SWIG_FILE_WITH_INIT
  7. /* Includes the header in the wrapper code */
  8. #include "cos_doubles.h"
  9. %}
  10. /* include the numpy typemaps */
  11. %include "numpy.i"
  12. /* need this for correct module initialization */
  13. %init %{
  14. import_array();
  15. %}
  16. /* typemaps for the two arrays, the second will be modified in-place */
  17. %apply (double* IN_ARRAY1, int DIM1) {(double * in_array, int size_in)}
  18. %apply (double* INPLACE_ARRAY1, int DIM1) {(double * out_array, int size_out)}
  19. /* Wrapper for cos_doubles that massages the types */
  20. %inline %{
  21. /* takes as input two numpy arrays */
  22. void cos_doubles_func(double * in_array, int size_in, double * out_array, int size_out) {
  23. /* calls the original funcion, providing only the size of the first */
  24. cos_doubles(in_array, out_array, size_in);
  25. }
  26. %}
  • 要使用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 [ ]:

  1. from distutils.core import setup, Extension
  2. import numpy
  3. setup(ext_modules=[Extension("_cos_doubles",
  4. sources=["cos_doubles.c", "cos_doubles.i"],
  5. include_dirs=[numpy.get_include()])])

和前面一样,我们需要用include_dirs来制定位置。

In [ ]:

  1. $ ls
  2. cos_doubles.c cos_doubles.h cos_doubles.i numpy.i setup.py test_cos_doubles.py
  3. $ python setup.py build_ext -i
  4. running build_ext
  5. building '_cos_doubles' extension
  6. swigging cos_doubles.i to cos_doubles_wrap.c
  7. swig -python -o cos_doubles_wrap.c cos_doubles.i
  8. cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found.
  9. cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found.
  10. cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found.
  11. creating build
  12. creating build/temp.linux-x86_64-2.7
  13. gcc -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.o
  14. gcc -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.o
  15. In file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722,
  16. from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17,
  17. from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15,
  18. from cos_doubles_wrap.c:2706:
  19. /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"
  20. 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
  21. $ ls
  22. build/ cos_doubles.h cos_doubles.py cos_doubles_wrap.c setup.py
  23. cos_doubles.c cos_doubles.i _cos_doubles.so* numpy.i test_cos_doubles.py

并且,和前面一样,我们来验证一下它工作正常:

In [ ]:

  1. import numpy as np
  2. import pylab
  3. import cos_doubles
  4. x = np.arange(0, 2 * np.pi, 0.1)
  5. y = np.empty_like(x)
  6. cos_doubles.cos_doubles_func(x, y)
  7. pylab.plot(x, y)
  8. pylab.show()

2.8.4 SWIG - 图1