boost.python笔记

标签: boost.python,python, C++


简介

Boost.python是什么?

它是boost库的一部分,随boost一起安装,用来实现C++和Python代码的交互。

使用Boost.python有什么特点?

不需要修改原有的C++代码,支持比较丰富的C++特性。不会生成额外的python代码(像SWIG那样),但是需要写一部分C++的封装代码。

我只用到了其功能的一部分,把C/C++实现的功能封装为可供python直接调用的.so库。具体场景是,有一个C++模块通过thrift封装为RPC,python代码通过PRC调用请求服务。由于调用频次较多,RPC调用开销成为一个很耗时的部分,因此想直接通过python对原模块功能进行调用。

以前了解SWIG可以实现这个需求。本已开始看SWIG的文档,但突然又想看一下还有没有别的方法,于是在stack overflow上搜了一些问题,发现不少人推荐Boost.python,于是打算拿它来试一试。

如何使用Boost.python

首先,前提是安装了开发环境。

  1. 安装了boost开发环境。安装了头文件和动态库。
  2. 安装了python开发环境。安装了头文件和动态库。

然后,就是写代码了,这是个没办法避免的事情!!!

需要自己动手的有两个地方。一个是xxxxxx_wrapper.cpp文件,文件名无所谓,其核心目的是定义导出的python模块的名称,以及需要导出的类、函数等。另一个是需要修改一下你的Makefile,来编译、链接这个so库。

先来看一下xxxxxx_wrapper.cpp。一般情况下,它的内容跟下面的代码比较接近。

#include <boost/python.hpp>
// 其他需要包含的头文件,与具体业务有关 namespace py = boost::python; // 其他函数,可能包括一些用于类型转换和封装的 BOOST_PYTHON_MODULE(my_module_name)
{
// 导出普通函数
def("fun_name_in_python", &fun_name_in_c); // 导出类及部分成员
class_<ClassNameInCpp>("ClassNameInPython", init<std::string>()) //类名,默认构造函数
.def(init<double>()) //其他构造函数
.def("memberFunNameInPython", &ClassNameInCpp::memberFunNameInCpp) //成员函数
.def_readwrite("dataMemberInPython", &ClassNameInCpp::dataMemberInCpp) //普通成员变量
.def_readonly("dataMemberInPython_2", &ClassNameInCpp::dataMemberInCpp_2) //只读成员变量
;
}

这个文件的核心目的体现在BOOST_PYTHON_MODULE里,定义需要导出给python的东西。

在Makefile里,需要增加一条用来编译导出的.so的规则,编译命令里通用的部分一般像下面这样,这里把编译和链接写在一起了。

g++ -o my_module_name.so -shared -fPIC -I${BOOST_INCLUDE_PATH} -I${PYTHON_INCLUDE_PATH} -L${BOOST_LIB_DIR} -lboost_python ${MY_SRC_FILES}

编译及链接参数的作用如下,其他参数由具体项目的业务逻辑决定:

  1. -o my_module_name.so,这里的模块名需要和xxxxxx_wrapper.cpp文件里BOOST_PYTHON_MODULE(my_module_name)一致;
  2. -I${BOOST_INCLUDE_PATH} -I${PYTHON_INCLUDE_PATH}是编译需要的;
  3. -L${BOOST_LIB_DIR} -lboost_python -shared -fPIC是链接需要的;
  4. ${MY_SRC_FILES}包含了xxxxxxx_wrapper.cpp以及业务逻辑需要的其他.cpp,.c文件;

至此,我们便有了my_module_name.so这个可以被python调用的模块了。测试一下吧。

>>>import my_moduel_name
>>>help(my_module_name)

可以看到被导出的类及函数,然后可以按照python的习惯来使用这些类和函数了。

如何写wrapper

以一个实例为框架来解释吧,内容包括普通函数、类、数据成员、成员函数、通过参数传递结果、容器。其他特性没有用到,也没有测试。

先来看一下业务逻辑的代码。包含一个类,一个以类对象为参数的函数,一个通过引用修改类对象的函数。

//test_class.h

// 定义一个类
class A
{
public:
A(){privateVal=0;} //默认构造函数
A(int val){privateVal=val;} //带参数的构造函数
void set(int val){privateVal=val;} //成员函数
int get() const {return privateVal;}; //成员函数
int publicVal; //公共数据成员
private:
int privateVal; //私有数据成员
}; int addA(A &a, int addVal); //普通函数,有返回值,通过引用修改参数
void printA(const A& a);
//test_class.cpp
#include <stdio.h>
#include "test_class.h" int addA(A &a, int addVal)
{
int val = a.get();
val += addVal;
a.set(val);
return val;
}
void printA(const A& a)
{
printf("%d\n", a.get());
}

然后是wrapper.cpp文件,这里实际名为test_class_wrapper.cpp。

//test_class_wrapper.cpp
#include <boost/python.hpp>
#include "test_class.h" BOOST_PYTHON_MODULE(test_class)
{
using namespace boost::python;
// 导出类
class_<A>("A", init<>()) //如果默认构造函数没有参数,可以省略
.def(init<int>()) //其他构造函数
.def("get", &A::get) //成员函数
.def("set", &A::set) //成员函数
.def_readwrite("publicVal", &A::publicVal) //数据成员,当然是公共的
;
def("printA", &printA);
def("addA", &addA);
}

通过python命令行测试一下

>>>import test_class
>>>a = test_class.A(5)
>>>ret = addA(a, 10)
>>>print ret
15
>>>print a.get()
15

到目前为止,整个过程都很顺利。需要额外写的代码很少,也很规整,与某种IDL的写法接近,只需要“声明”一下,剩下的事情都交给编译器及库完成。但有的时候,这个过程就不这么顺利了,我们需要额外写一些转换及封装。比如在这一部分最开始提到的容器,上面的代码就没有涉及。

下面的代码,我们对上面的例子做了一些扩展。第一,对类A增加了一个vector成员,需要在python代码里引用该成员;第二,增加了一个函数,以vector为参数,需要在python代码里直接调用该函数。下面我们就来解释与容器有关的导出。

#include<vector>

class B;

class A
{
public:
A(){privateVal=0;} //默认构造函数
A(int val){privateVal=val;} //带参数的构造函数
void set(int val){privateVal=val;} //成员函数
int get() const {return privateVal;}; //成员函数
int publicVal; //公共数据成员
std::vector<B> m_vB;
private:
int privateVal; //私有数据成员
}; class B
{
public:
B(){}
~B(){}
int pos;
int len;
}; int accumulate(const std::vector<A>& v_A);
int addA(A &a, int addVal); //普通函数,有返回值,通过引用修改参数
void printA(const A& a);
#include <stdio.h>
#include "test_class.h" int addA(A &a, int addVal)
{
int val = a.get();
val += addVal;
a.set(val);
return val;
}
void printA(const A& a)
{
printf("%d\n", a.get());
} int accumulate(const std::vector<A>& v_A)
{
int ret = 0;
for (size_t i = 0; i < v_A.size(); i++)
{
ret += v_A[i].get();
}
return ret;
}

首先,需要明白一点,c++中的vector不等于python中的list,虽然它们看上去比较相似。Boost.python中有与python的list对应的东西,是boost::python::list,如果在python代码里以list为参数调用某个方法,则在c++代码中这个参数被自动映射为boost::python::list,不是vector。既然这样,如果我们不打算修改原有的C++代码,又想调用以vector为参数的函数,该怎么办呢?

目前我了解的方法由两种:

  1. 在C++代码里对以vector为参数的函数进行一层封装,封装为以boost::python::list为参数的函数,导出封装后的函数。在函数里通过boost::python::extract_<T>对list里的所有成员进行提取,将其由boost::python::object对象变为T类型的对象,然后存于vector<T>中,再调用以vector<T>为参数的函数。

    如果需要返回list或者原函数对vector参数的内容作了修改,需要再将调用函数后的vector内的每个元素放回list里。
  2. 直接导出vector<T>类型。此时vector<T>本身作为一个类型被导出给python代码,与普通的类具有同等地位。但是,与普通类不同的是,它通过模板vector_indexing_suite<std::vector<T> >()导出,自动实现了append,slice,__len__等方法,在python里可以像使用list那样操作这个被导出的vector类。而且,以vector<T>为参数的函数,在通过def导出给python时,其参数会被自动映射为vector_indexing_suite<std::vector<T> >。同理,python代码传入的通过vector_indexing_suite导出的容器对象,也会在c++代码里被自动转换为vector,这里无需显式地写转换函数。

下面的代码是wrapper文件,使用第二种方法,即直接导出vector类型。

为什么这么做呢?因为A里有个vector成员,要导出这个成员,必须导出这个vector<B>这个类型,否则还需要对类A再做一层封装,让它包含一个boost::python::list成员,这就太麻烦了。

#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include "test_class.h" bool operator==(const B& left, const B& right); bool operator==(const A& left, const A& right)
{
if (left.get() != right.get() || left.publicVal != right.publicVal)
return false;
if (left.m_vB.size() != right.m_vB.size())
return false;
for (size_t i = 0; i < left.m_vB.size(); i ++)
{
if (!(left.m_vB[i] == right.m_vB[i]))
return false;
}
return true;
} bool operator==(const B& left, const B& right)
{
return (left.pos == right.pos && left.len == right.len);
} BOOST_PYTHON_MODULE(test_class)
{
using namespace boost::python;
class_<A>("A", init<>()) //如果默认构造函数没有参数,可以省略
.def(init<int>()) //其他构造函数
.def("get", &A::get) //成员函数
.def("set", &A::set) //成员函数
.def_readwrite("publicVal", &A::publicVal) //数据成员,当然是公共的
.def_readwrite("vB", &A::m_vB)
;
class_<std::vector<A> >("VecA")
.def(vector_indexing_suite<std::vector<A> >())
;
class_<B>("B")
.def_readwrite("pos", &B::pos)
.def_readwrite("len", &B::len)
;
class_<std::vector<B> >("VecB")
.def(vector_indexing_suite<std::vector<B> >())
;
def("printA", &printA);
def("addA", &addA);
def("accumulate", &accumulate);
}

我们还是从BOOST_PYTHON_MODULE内的代码开始看。

  • class_<A>的定义看上去和前面的例子没有太大差别,只是多导出了一个成员.def_readwrite("vB", &A::m_vB)。即使这个成员变量是vector<B>类型的,在这里也不需要特殊对待;
  • class_<B>就是导出一个类,包含两个共有数据成员。这里也没什么特别的;
  • class_<std::vector<A> >("VecA")class_<std::vector<B> >("VecB")是本例的重点,导出了两个不同的vector类型,因为在c++里,vector是一个类模板,vector<A>vector<B>才是两个具体的类型;
  • printAaddAaccumulate是三个导出的函数。即使其参数是vector<A>类型,也无需特别对待;

除此之外,注意到为类型A和类型B定义了==操作符,这是boost.python在导出某种类型的vector时需要的,在内部某个地方用到了==操作符。如果仅导出类型,不导出类型的向量,是不需要==操作符的,如前面的例子所示。

编译链接后,通过python命令行测试一下:

>>>import test_class
>>>a1 = test_class.A()
>>>b1 = test_class.B() # 实例化一个B
>>>b1.pos = 1
>>>b1.len = 1
>>>b2 = test_class.B() # 实例化另一个B
>>>b2.pos = 2
>>>b2.len = 2
>>>a1.vB.append(b1) # a1.vB是vector<B>在python中对应类型的对象,接口类似list,但只能添加B类型的对象
>>>a1.vB.append(b2)
>>>print a1.vB[-1].len # a1.vB支持list的下标引用
2
>>>a1.set(1)
>>>a2 = test_class.A(2)
>>>a3 = test_class.A(3)
>>>vA = test_class.VecA() # vector<A>在python中对应的类型
>>>vA.append(a1)
>>>vA.append(a2)
>>>vA.append(a3)
>>>print accumulate(vA) # 调用以vector<A>为参数的函数
6

对于导出的python模块来说,一切在python中会被引用到的变量,其所属类型(基本数据类型除外)都需要被明确导出,也就是都需要在BOOST_PYTHON_MODULE里被定义。如本例中的vector<B>,尽管没有函数以该类型为参数,但如果想要在python代码里引用A的成员vB,就需要导出它,否则会抛异常。相反,如果不需要在python代码里引用这个成员,则不需要导出vector<B>这个类型,而且在class_<A>的定义中也应把.def_readwrite("vB", &A::m_vB)去掉。如果不导出vector<B>类型,但在class_<A>的定义中通过.def_readwrite("vB", &A::m_vB)导出了该成员,编译不会出问题,使用python模块也不会出问题,但只要代码引用到A.vB就会抛异常,相当于埋了一个坑。

从实用的角度看,这样一个流程可能会比较有效。首先,确定需要导出的函数及类型。然后检查函数(包括成员函数)参数及返回值的类型,非基本类型需要被导出;检查导出的类成员变量,如果不是基本类型,其类型也要导出。如此直到没有新的类型需要被添加为止。

总结

Boost.python的文档感觉比较少,很多问题和trick都是在stack overflow上看到然后再试验的。据了解,Boost.python支持更为丰富的c++特性,这里只用到了一小部分。

boost.python笔记的更多相关文章

  1. python学习笔记:安装boost python库以及使用boost.python库封装

    学习是一个累积的过程.在这个过程中,我们不仅要学习新的知识,还需要将以前学到的知识进行回顾总结. 前面讲述了Python使用ctypes直接调用动态库和使用Python的C语言API封装C函数, C+ ...

  2. C++ boost.python折腾笔记

    为了让当年研究生时写的图像处理系统重出江湖起到更大的作用,应研究生导师的意见,对原有的c++框架做了python扩展处理,为了避免遗忘,备注如下: 一.boost 编译 下载boost源码,这里使用b ...

  3. Boost Python学习笔记(四)

    你将学到什么 在Python中调用C++代码时的传参问题 基础类型 Python的字符串是常量,所以C++函数参数中的std::string &必须为const 修改源文件(main.cpp) ...

  4. Boost Python学习笔记(五)

    你将学到什么 在C++中调用Python代码时的返回值问题 基础类型 修改Python脚本(build/zoo.py) def rint(): return 2 def rstr(): return ...

  5. Boost Python学习笔记(二)

    你将学到什么 如何在Python中调用C++代码 如何在C++中调用Python代码 在Python中调用C++代码 首先定义一个动物类(include/animal.h) #pragma once ...

  6. Boost Python学习笔记(三)

    你将学到什么 在C++中调用Python代码时的传参问题 基础类型 继续使用前面的项目,但是先修改下Python脚本(zoo.py),添加Add和Str函数,分别针对整数.浮点数和字符串参数的测试 d ...

  7. boost 学习笔记 0: 安装环境

    boost 学习笔记 0: 安装环境 最完整的教程 http://einverne.github.io/post/2015/12/boost-learning-note-0.html Linux 自动 ...

  8. Python笔记之不可不练

    如果您已经有了一定的Python编程基础,那么本文就是为您的编程能力锦上添花,如果您刚刚开始对Python有一点点兴趣,不怕,Python的重点基础知识已经总结在博文<Python笔记之不可不知 ...

  9. Boost.Python简介

    Boost.Python简单概括:是Boost库的一部分:用来在C++代码中调用python代码以及在Python代码中调用C++代码,并且避免用户直接操作指针. 以下内容搬运自:https://wi ...

随机推荐

  1. 【夯实PHP基础】PHP常用类和函数总结

    本文地址 代码提纲: 1. 字符串处理类及函数 2. 数组处理类及函数 3 .web处理类及函数 将常用的PHP的类和函数总结到这里,主要是 自己用过的,比较有感觉. 1. [字符串处理] 1)[ut ...

  2. 关于CSS inline-block、BFC以及外边距合并的几个小问题

    CSS inline-block和BCF对于初学者来说,总是弄不太明白,下面记录下我在学习这块知识的过程中遇到的几个问题,供大家参考,有不足的地方,欢迎大家批评指正. 一.在什么场景下会出现外边距合并 ...

  3. Atitit.软件开发的三层结构isv金字塔模型

    Atitit.软件开发的三层结构isv金字塔模型 第一层,Implements 层,着重与功能的实现.. 第二次,spec层,理论层,设计规范,接口,等.流程.方法论 顶层,val层,价值观层,原则, ...

  4. github中的watch、star、fork的作用

    [转自:http://www.jianshu.com/p/6c366b53ea41] 在每个 github 项目的右上角,都有三个按钮,分别是 watch.star.fork,但是有些刚开始使用 gi ...

  5. BZOJ 1391: [Ceoi2008]order [最小割]

    1391: [Ceoi2008]order Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1509  Solved: 460[Submit][Statu ...

  6. 二叉树的创建和遍历(C版和java版)

    以这颗树为例:#表示空节点前序遍历(根->左->右)为:ABD##E##C#F## 中序遍历(左->根->右)为:#D#B#E#A#C#F# 后序遍历(左->右-> ...

  7. angular中使用ngResource模块构建RESTful架构

    ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入 ...

  8. 【译】Meteor 新手教程:在排行榜上添加新特性

    原文:http://danneu.com/posts/6-meteor-tutorial-for-fellow-noobs-adding-features-to-the-leaderboard-dem ...

  9. 作为前端应当了解的Web缓存知识

    缓存优点 通常所说的Web缓存指的是可以自动保存常见http请求副本的http设备.对于前端开发者来说,浏览器充当了重要角色.除此外常见的还有各种各样的代理服务器也可以做缓存.当Web请求到达缓存时, ...

  10. 站在风口,你或许就是那年薪20w+的程序猿

    最近面试了一些人,也在群上跟一些群友聊起,发现现在的互联网真是热,一些工作才两三年的期望的薪资都是十几K的起,这真是让我们这些早几年就成为程序猿的情何以堪!正所谓是站在风口上,猪也能飞起来!我在这里就 ...