1、引言
Python Wraps是Python语言中非常强大的功能,它允许用户通过使用已经编写好的代码,将现有的底层代码集成在自己编写的高级代码中,简化编程步骤,提高编程效率。本文将从基本概念、代码实现、使用案例等方面深入浅出地介绍Python Wraps的入门指南。
2、Python Wraps基本概念
1、Python Wraps定义和作用
Python Wraps是指Python中的一个库,它是Python语言中经典的C/C++扩展方式之一,也是Python中与其他语言编写的函数互操作的重要工具之一,其主要用途是将C/C++函数或对象封装成Python模块。
Python Wraps能够极大地扩展Python的功能,使得Python语言在使用底层C/C++代码时方便很多。例如,如果我们想在Python中使用一个性能非常棒的C/C++函数,只需要使用Python Wraps将原始的C/C++函数封装到Python模块中,就可以在Python中调用它了,而不必再去写C/C++的代码。
2、Python Wraps实现方式
Python Wraps的实现方式有两种:手动编写和自动化构建。手动编写是指将C/C++函数或对象封装到Python模块中,需要自己按照Python API编写底层代码,使用手动编写能够更加灵活,但是需要一定的编程能力;自动化构建则是利用已经存在的工具生成Python Wraps代码,例如SWIG、Cython等,这种方式虽然不需要编写Python API,但是有些情况下会导致一些问题。
SWIG(Simplified Wrapper and Interface Generator)可以自动生成支持多种语言的Wraps代码,包括C/C++、Python、Java、Ruby等。Cython是另一种自动生成Wraps代码的工具,它可以将Python代码和C/C++代码混合编写。
3、Python Wraps代码实现
1、手动编写Python Wraps
下面以一个简单的例子来介绍手动编写Python Wraps的过程。
(1)原始的C++函数
int add(int a, int b) { return a + b; }
(2)将C++函数封装到Python模块
将C++函数封装到Python模块需要使用Python C API编写代码,具体代码如下:
#include <Python.h> static PyObject *add_wrapper(PyObject *self, PyObject *args) { int a, b; if (!PyArg_ParseTuple(args, "ii", &a, &b)) return NULL; return Py_BuildValue("i", add(a, b)); } static PyMethodDef add_methods[] = { {"add", add_wrapper, METH_VARARGS, "Add two integers."}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef add_module = { PyModuleDef_HEAD_INIT, "add", /* name of module */ NULL, /* module documentation, may be NULL */ -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ add_methods }; PyMODINIT_FUNC PyInit_add(void) { return PyModule_Create(&add_module); }
以上代码中,add_wrapper函数是一个Python函数,它调用了原始的C++函数add,并将计算结果作为Python对象返回;add_methods是一个Python方法,它定义了Python模块包含的函数;PyModuleDef和PyInit_add是两个Python C API便利函数,它们可以将Python模块转化为C/C++模块并导出为Python模块。
2、自动化构建Python Wraps
SWIG和Cython是两个知名的自动生成Python Wraps的工具。下面分别介绍SWIG和Cython的使用方法。
(1)SWIG工具
SWIG工具可以使用SWIG接口定义语言(Interface Definition Language,IDL)编写Wraps代码。SWIG支持多种语言,例如C++, Java, Perl, Python, Ruby, Tcl等。本例中,我们使用SWIG自动生成Python Wraps代码。
① 安装SWIG
下载并安装SWIG。SWIG工具可以从它的官方网站http://www.swig.org/download.html上下载。不同的操作系统有不同的安装方式,具体安装过程请参考官方文档。
② 编写SWIG文件
在SWIG文件中,需要定义C/C++函数,并使用SWIG标记指定输入参数、输出参数以及参数类型。下面是一个简单的例子,输出一个整数值:
/* file: example.i */ %module example %{ #include "example.h" %} %include "example.h" int add(int a, int b);
以上的SWIG文件使用了C++头文件example.h中的add函数,并且添加了一个Python模块example。函数add是在Python模块中可用的C++函数。
将以上的SWIG文件example.i保存到一个目录下。
③ 使用SWIG生成Python Wraps代码
使用SWIG生成Python Wraps代码需要保证目录下有一个example.h文件和上面的example.i文件。在命令行中输入以下命令:
$ swig -c++ -python example.i
以上命令将会生成一个C++源代码example_wrap.cxx和一个Python Wraps代码example.py。其中example_wrap.cxx就是Python Wraps的底层代码,example.py是Python模块的使用接口,包含对Python Wraps函数的调用语法。
④ 编译Python模块
Python Wraps代码生成后需要编译,编译命令如下:
$ g++ -c -Wall example_wrap.cxx -I/usr/include/python3.6 $ g++ -shared example_wrap.o -o _example.so
在example.py所在的目录下,执行Python交互环境,便可使用Python模块example以及其中的函数add了。
(2)Cython工具
Cython工具可以使用Python代码和C/C++代码混合编写Wraps代码。Cython会将Python代码转换成C/C++代码,使得Python代码可以访问底层的C/C++代码。下面是一个简单的例子,输出一个整数值:
### file: example.pyx cdef extern from "example.h": int add(int a, int b) def add_py(int a, int b): cdef int c c = add(a, b) return c
以上代码使用Cython的extern机制,通过定义C/C++的头文件example.h,将底层C/C++代码导入Cython代码中,然后通过Python定义的高级封装add_py函数对其封装,便于在Python代码中使用。
编译Python模块:
$ cython example.pyx $ gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing -I/usr/include/python3.4m example.c -o example.so
在example.py所在的目录下,执行Python交互环境,便可使用Python模块example以及其中的函数add_py了。
4、Python Wraps使用案例
以下是一个简单的使用Python Wraps的案例,使用numpy库辅助绘制二维点。该库使用C++实现的多项式拟合,并使用Python Wraps封装为Python模块调用。下面分别介绍手动编写Python Wraps和自动化构建Python Wraps的实现方式。
1、手动编写Python Wraps
以下是多项式拟合C++实现的头文件polyfit.h,以及我们需要封装成Python模块的add函数:
#ifndef POLYFIT_H #define POLYFIT_H #include <vector> double polyfit(const std::vector<double> &x, const std::vector<double> &y, int order); int add(int a, int b); #endif
将add函数封装为Python模块,代码如下:
#include <Python.h> #include <numpy/ndarrayobject.h> #include "polyfit.h" static PyObject* py_add(PyObject* self, PyObject* args) { int a, b; if (!PyArg_ParseTuple(args, "ii", &a, &b)) { return NULL; } return Py_BuildValue("i", add(a, b)); } /* Wraps module functions */ static PyMethodDef polyfit_methods[] = { {"add", (PyCFunction) py_add, METH_VARARGS, "add two integers"}, {NULL, NULL, 0, NULL} }; /* Module initialization */ PyMODINIT_FUNC initpolyfit(void) { PyObject *m = Py_InitModule3("polyfit", polyfit_methods, "polyfit module"); import_array(); }
编译Python模块:
$ g++ -O3 -Wall -shared -std=c++11 -fPIC `python2.7-config --includes`\ `python2.7-config --libs` -I/usr/include/python2.7\ -o polyfit.so polyfit_wrap.cxx
在Python交互环境中:
import polyfit polyfit.add(1, 2) # 输出 3
2、自动化构建Python Wraps
为了演示自动化构建Python Wraps,我们使用SWIG完成numpy.polyfit的封装。以下是使用SWIG封装后的numpy.polyfit代码,其核心代码polyfit.i如下:
%module polyfit %{ #include "polyfit.h" %} %include <numpy.i> %numpy_typemaps(double, NPY_DOUBLE) %apply (double *IN_ARRAY1, int DIM1, double *IN_ARRAY2, int DIM2, int, double *OUT_ARRAY, int DIM3) { (double *x, int x_size, double *y, int y_size, int order, double *coefficients, int coefficients_size) } %{ static PyObject* py_polyfit(PyObject* self, PyObject* args) { PyObject *x_obj, *y_obj; int order; if(!PyArg_ParseTuple(args, "OOi", &x_obj, &y_obj, &order)) { return NULL; } int x_size = PyArray_DIM(x_obj, 0); int y_size = PyArray_DIM(y_obj, 0); npy_double *x = (npy_double*)PyArray_DATA(x_obj); npy_double *y = (npy_double*)PyArray_DATA(y_obj); npy_intp shape[1] = {order + 1}; PyObject* coefficients_object = PyArray_SimpleNew(1, shape, NPY_DOUBLE); npy_double *coefficients = (double*)PyArray_DATA((PyArrayObject*)coefficients_object); polyfit(x, x_size, y, y_size, order, coefficients, order + 1); return coefficients_object; } %} %include "polyfit.h"
SWIG将polyfit.i文件转换为C++源代码:
$ swig -c++ -python polyfit.i
将生成的polyfit_wrap.cxx文件编译为Python模块:
$ g++ -O3 -Wall -shared -std=c++11 -fPIC `python2.7-config --includes`\ `python2.7-config --libs` -I/usr/include/python2.7\ -o _poly