您的位置:

Python Wraps入门指南

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