一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

c/c++扩展python实例学习教程

时间:2015-08-08 编辑:简简单单 来源:一聚教程网

用python脚本写应用比较方便,本文我们将学习用c/c++python扩展的实例。



说明: 测试环境中操作系统为CentOS6.5_x64,python版本为2.6


直接调用动态库

1、编写模块动态库文代码

这里以求最大数为示例

代码(callTest1.cpp)如下:


extern "C"
{
    int Max(int i1,int i2)
    {
        return (i1>i2)?i1:i2;
    }
}  



在bash中执行以下命令:

g++ -fPIC -shared -o libcallTest1.so callTest1.cpp

生成动态库文件libcallTest1.so

2、使用python调用动态库

可以通过ctypes调用动态库文件,具体如下:


#! /usr/bin/env python

from ctypes import *
import os

so1 = CDLL(os.getcwd() + '/libcallTest1.so')
print so1.Max(1,3)


使用boost库扩展python

这种方式可以直接生成python模块,使用import操作直接导入即可。 当然使用这种方式,需要安装boost库,具体操作这里就不说了,不懂的朋友自己查下。

boost官网:http://www.boost.org/

1、模块代码如下:

文件名:boostCallTest1.cpp

文件内容:


int Max(int i1,int i2)
{
    return (i1>i2)?i1:i2;
}

#include
#include
using namespace boost::python;

BOOST_PYTHON_MODULE(boostCallTest1)
{
    def("Max",Max);
}



2、编写模块编译脚本

文件名: setup.py

文件内容:


#!/usr/bin/env python

from distutils.core import setup
from distutils.extension import Extension

setup(name="PackageName",
    ext_modules=[
    Extension("boostCallTest1", ["boostCallTest1.cpp"],
    libraries = ["boost_python"])
])



3、编译模块并测试

编译模块:

python setup.py build

会在build目录产生boostCallTest1.so文件,进入该目录,可如下使用:

>>> import boostCallTest1
>>> boostCallTest1.Max(1,3)
3

使用swig扩展python

SWIG是个帮助使用C或者C++编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。

SWIG能应用于各种不同类型的语言包括常用脚本编译语言例如Perl, PHP, Python, Tcl, Ruby and PHP。

swig官网: http://www.swig.org/download.html

可以通过yum直接安装:

yum install swig

1、编写程序代码

文件名:swigCall.cpp

内容如下:

int Max(int i1,int i2)
{
    return (i1>i2)?i1:i2;
}

2、编写接口文件

文件名:swigCall.i

内容如下:

%module swigCall
%{
extern int Max(int i1,int i2);
%}

extern int Max(int i1,int i2);

3、编写Makefile文件

内容如下:

all:
    make swigCall

swigCall:
    swig -python -c++ swigCall.i
    g++ -c swigCall.cpp swigCall_wrap.cxx -I/usr/include/python2.6/ -fPIC
    g++ -shared swigCall.o swigCall_wrap.o -o _swigCall.so

clean:
    rm -f swigCall_wrap.c*
    rm -f *.py*
    rm -f *.so
    rm -f *.o

注意:swig命令中要使用-c++参数编译c++代码


使用SIP扩展python

SIP是从SWIG发展而来,专为Python调用C/C++模块使用的。 注意,这里的SIP和voip中的sip不是同一个东西,这里的sip是扩展python用的,voip中的sip是一个通信协议,不要搞混了。

注意:

需要安装python sip库;

1、编写c++模块

1.1 编写头文件

文件名: sipCall.h 文件内容:

class TestMode
{
public:  
    int Max(int i1,int i2);
};

1.2 编写模块内容

文件名:sipCall.cpp 文件内容:


#include "sipCall.h"  

int TestMode::Max(int i1,int i2)
{
    return (i1>i2)?i1:i2;
}



2、编写接口文件

文件名:sipCall.sip 文件内容:

%Module TestMode

class TestMode {  

%TypeHeaderCode  
#include "sipCall.h"  
%End  

public:  
    int Max(int i1,int i2);
};  

3、生存静态库

这里用脚本实现,文件名称:genlib.sh

文件内容:

#! /bin/bash
g++ -c -fPIC sipCall.cpp
ar -crs libsipCall.a sipCall.o

4、编写configure文件

该脚本用于生成Makefile,内容如下:


#! /usr/bin/env python

import os  
import sipconfig  

build_file = "sipCall.sbf"
config = sipconfig.Configuration()
cmd = " ".join([config.sip_bin, "-c", ".", "-b", build_file, "sipCall.sip"])  
os.system(cmd)  

makefile = sipconfig.SIPModuleMakefile(config, build_file)
makefile.extra_libs = ["sipCall"]  
makefile.LIBDIR.append(".")  
makefile.generate()  



5、运行genlib.sh脚本生成lib文件;

6、运行configure.py脚本生成Makefile;

7、运行make生成模块文件(so文件);

8、python测试如下:

>>> import TestMode
>>> dir(TestMode)
['TestMode', '__doc__', '__file__', '__name__', '__package__']
>>> s = TestMode.TestMode()
>>> s.Max(1,3)
3

本文github地址:

https://github.com/mike-zhang/mikeBlogEssays/blob/master/2015/20150808_用c++扩展python.md

Python的C/C++扩展

可扩展性是Python的一大特色,一方面,由于Python是解释执行的,这导致运行速度会比编译型语言慢,因此可以通过使用C/C++重写核心部分代码以解决性能上的瓶颈(程序90%的时间再运行10%的代码);另一方面,可以通过扩展,达到添加整合一些额外的功能以及保持专有源代码的目的。在本文接下来的部分中我们将讨论如何编写C/C++扩展代码,并使用它们的功能。
我们要建立的是一个可以在Python内运行的C/C++模块,因此需要解决如何使C代码和Python代码能够进行交互以及数据共享。扩展是通过为C代码编写包装函数(类似适配器)实现双向交互和数据共享的。

一.一般的包装模式

每一个包装函数主要做三件事:
1.把输入的Python对象转换为C/C++对象;
2.调用C/C++函数;
3.转换C/C++函数处理的输出结果为Python对象,并返回;

先用一个简单样例描述模块大体的过程:

//wrap.cpp
//1.C代码
#include "Python.h"
int add(int arg1, int arg2)
{
    return arg1 + arg2;
}
//2.add的包装函数:
static PyObject* wrap_add(PyObject *self, PyObject *args)
{
    //把输入的Python对象转换为C/C++能识别的数据
    int arg1, arg2;
    if(!PyArg_ParseTuple(args, "ii", &arg1, &arg2))
    return NULL;
    //调用C/C++函数,得到结果
    int result = add(arg1,arg2);
    //把得到的结果包装成Python对象,并返回
    return (PyObject*)Py_BuildValue("i", result);
}
//3.为模块添加PyMethodDef方法数组
static PyMethodDef wrap_methods[] ={
    {"add", wrap_add, METH_VARARGS},
    {NULL, NULL}
};
//4.增加模块初始化函数InitModule
PyMODINIT_FUNC initwrap (void)
{
    Py_InitModule("wrap ", wrap_methods);
}


   
把上面的代码编译,生成wrap.pyd。

启动控制台切换到相应的工程目录,即可测试生成的模块:

未命名.jpg

(PS:似乎只有Release模式生成的模块才能正常运行)

相关说明

每个包装函数都有如下形式:
     PyObject * wrap_function(PyObject *, PyObject * args)
函数第一个参数,有特殊用途,通常选择忽略。第二个参数是一个PyTuple(PyObject的子类型,和Python中的Tuple对应),是调用时Python传入的参数。
函数PyArg_ParseTuple把Python对象转换为C的数据类型,其声明如下:
    int PyArg_ParseTuple(PyObject* args, char* format, ...);
参数args必须是一个tuple对象,包含传递过来的参数, format 参数必须是格式化字符串。剩余参数是各个变量的地址,类型要与格式化字符串对应。如:
    int arg1, arg2;
    PyArg_ParseTuple(args, "ii", &arg1, &arg2);
函数Py_BuildValue可以说是PyArg_ParseTuple的逆过程,它把C的数据类型包装为Python对象。
    return (PyObject*)Py_BuildValue("i", result);
把调用C函数的结果result包装为Python的int对象,并返回。
    static PyMethodDef wrap_methods[] ={
        {"add", wrap_add, METH_VARARGS},
        {NULL, NULL}
    };
这个数组包含多个数组,其中的每个数组都包含了一个函数的信息,以便解释器能够导入并调用它们,最后一个NULL数组表示列表的结束。METH_VARARGS常量表示参数以元组形式传入。
    PyMODINIT_FUNC initwrap (void)
    {
        Py_InitModule("wrap ", wrap_methods);
    }
模块初始化函数void initModuleName(),这部分代码在模块被导入的时候被解释器调用。这样所有的包装就已经完成了。

二.C++类的包装

// Example.cpp
class Numbers
{
public:
    Numbers(int first, double second)
        : m_first( first), m_second(second){}
    double NumMemberMult(void){ return m_first*m_second;}
private:
    int m_first;
    double m_second;
};
static void PyDelNumbers(void *ptr)
{
    Numbers * oldnum = static_cast(ptr);
    delete oldnum;
    return;
}
PyObject *Example_new_Numbers(PyObject *, PyObject* args)
{
    int arg1;
    double arg2;
    int ok = PyArg_ParseTuple(args,"id",&arg1,&arg2);
    if(!ok) return NULL;
   //动态创建一个新对象
    Numbers *newnum = new Numbers(arg1, arg2);
   //把指针newnum包装成PyCObject对象并返回给解释器
    return PyCObject_FromVoidPtr( newnum, PyDelNumbers);
}
PyObject * Example_Numbers_MemberMult(PyObject *, PyObject* args)
{
    PyObject *pynum = 0;
    int ok = PyArg_ParseTuple( args, "O", &pynum);
    if(!ok) return NULL;
   //把PyCObject转换为void指针
    void * temp = PyCObject_AsVoidPtr(pynum);
   //把void指针转换为一个Numbers对象指针
    Numbers * thisnum = static_cast(temp);
    //调用函数
    double result = thisnum->NumMemberMult();
    //返回结果
    return Py_BuildValue("d",result);
}
static PyMethodDef Example_methods[] = {
    {"Numbers", Example_new_Numbers, METH_VARARGS},
    {"NumMemberMult", Example_Numbers_MemberMult, METH_VARARGS},
    {NULL, NULL}
};
PyMODINIT_FUNC initExample (void)
{
    Py_InitModule("Example", Example_methods);
}


C++类的包装和C函数的包装大同小异,因为对类的包装是对函数的包装,所以仍需要用Python代码对扩展模块进行包装,才可以像类一样的使用。

#example.py
from Example import *
class example(object):
    def __init__(self,arg1,arg2):
        self._base = Numbers(arg1,arg2)
    def MemberMult(self):
        return NumMemberMult(self._base)


未命名2.jpg


这样C++类的包装也完成了。

三.C/C++中创建Python list

static PyObject* Windy_dict(PyObject *self, PyObject *args)
{
    //创建列表
    PyObject *newlist = PyList_New(0);
    PyList_Append(newlist, PyString_FromString("first"));
    PyList_Append(newlist, PyString_FromString("second"));
    PyList_Append(newlist, PyString_FromString("third"));
    //返回给解释器
    return newlist;
}


创建其它Python对象也类似list的创建,返回给解释器的都是一个对象指针。C/C++对Python对象的解析差不多是创建时的逆过程。具体的对象模型及API可以查阅相关参考文档。

热门栏目