当前位置:

首页 > 编程开发 > boost.python笔记

boost.python笔记

Boost.python是Boost库的一部分,用于实现C++与Python无缝互操作,无需修改原有C++代码,但需手写封装代码。将C++模块封装为.so库,使Python直接调用,解决RPC调用性能瓶颈。通过定义导出模块、类、函数及成员变量,支持容器类型导出,如vector。

boost.python笔记

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

boost.python笔记


简介

Boost.python是什么?

它是boost库的一部分,随boost一起安装,专门用来打通C++和Python之间的壁垒,让这两门语言能无缝协作。

用Boost.python有什么特别之处?

最大的优点是不需要动你原有的C++代码,而且对C++的各种特性支持得相当好。它不会像SWIG那样生成额外的Python代码,但代价是需要你亲手写一部分C++封装代码。

这里只用到它能力的一小部分——把C/C++实现的功能封装成Python可以直接调用的.so库。具体场景是这样的:有一个C++模块通过thrift封装成了RPC服务,Python代码通过RPC调用来请求服务。问题在于调用频率太高,RPC的开销成了性能瓶颈,所以干脆让Python直接调用原模块的功能。

之前了解过SWIG可以实现这个需求,文档都开始看了。转念一想,有没有别的办法?在stack overflow上搜了一圈,发现不少人推荐Boost.python,于是决定拿它来试试水。

如何使用Boost.python

首先,前提是开发环境得配齐:

安装了boost开发环境,头文件和动态库都得有。
安装了python开发环境,头文件和动态库也得有。

然后就是写代码了,这一步怎么都绕不过去!

需要动手的地方有两个。一个是xxxxxx_wrapper.cpp文件——文件名随便起,核心目的是定义要导出的Python模块名称,以及要暴露出去的类、函数等等。另一个是修改你的Makefile,用来编译、链接这个.so库。

先看看xxxxxx_wrapper.cpp长什么样。一般情况下,它的内容跟下面的代码差不多。

#include // 其他需要包含的头文件,与具体业务有关 namespace py = boost::python; // 其他函数,可能包括一些用于类型转换和封装的 BOOST_PYTHON_MODULE(my_module_name) { // 导出普通函数 def("fun_name_in_python", &fun_name_in_c); // 导出类及部分成员 class_("ClassNameInPython", init()) //类名,默认构造函数 .def(init()) //其他构造函数 .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}

各个参数的作用如下,其他参数就得看具体项目的业务逻辑了:

-o my_module_name.so——这里的模块名必须和xxxxxx_wrapper.cpp里BOOST_PYTHON_MODULE(my_module_name)保持一致;
-I${BOOST_INCLUDE_PATH} -I${PYTHON_INCLUDE_PATH}是编译阶段需要的;
-L${BOOST_LIB_DIR} -lboost_python -shared -fPIC是链接阶段需要的;
${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 #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 #include "test_class.h" BOOST_PYTHON_MODULE(test_class){ using namespace boost::python; // 导出类 class_("A", init<>())//如果默认构造函数没有参数,可以省略 .def(init()) //其他构造函数 .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 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 m_vB; private: int privateVal; //私有数据成员 }; class B{ public: B(){} ~B(){} int pos; int len; }; int accumulate(const std::vector& v_A); int addA(A &a, int addVal); //普通函数,有返回值,通过引用修改参数 void printA(const A& a);

#include #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& 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为参数的函数,怎么办?

目前了解的方法有两种:

在C++代码里对以vector为参数的函数做一层封装,封装成以boost::python::list为参数的函数,然后导出封装后的函数。在函数里通过boost::python::extract_对list里的每个成员进行提取,把它们从boost::python::object对象变成T类型的对象,存入vector中,再调用以vector为参数的函数。如果需要返回list,或者原函数对vector参数的内容做了修改,还需要把调用函数后的vector里的每个元素再放回list里。

直接导出vector类型。这样一来,vector本身作为一个类型被导出给Python代码,跟普通的类平起平坐。但和普通类不同的是,它通过模板vector_indexing_suite >()导出,自动实现了append、slice、__len__等方法,在Python里可以像操作list那样操作这个导出的vector类。而且,以vector为参数的函数,在通过def导出给Python时,参数会被自动映射成vector_indexing_suite >。同理,Python代码传入的通过vector_indexing_suite导出的容器对象,也会在C++代码里被自动转换成vector,不需要显式写转换函数。

下面的wrapper代码使用了第二种方法——直接导出vector类型。

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

#include #include #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", init<>())//如果默认构造函数没有参数,可以省略 .def(init()) //其他构造函数 .def("get", &A::get)//成员函数 .def("set", &A::set)//成员函数 .def_readwrite("publicVal", &A::publicVal)//数据成员,当然是公共的 .def_readwrite("vB", &A::m_vB); class_ >("VecA") .def(vector_indexing_suite >()); class_("B") .def_readwrite("pos", &B::pos) .def_readwrite("len", &B::len); class_ >("VecB") .def(vector_indexing_suite >()); def("printA", &printA); def("addA", &addA); def("accumulate", &accumulate); }

BOOST_PYTHON_MODULE内的代码开始看:

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

另外,注意为类型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在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在python中对应的类型 >>>vA.append(a1) >>>vA.append(a2) >>>vA.append(a3) >>>print accumulate(vA)# 调用以vector为参数的函数 6

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

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

总结

Boost.python的官方文档感觉不算多,很多问题和trick都是在stack overflow上看到再自己试验的。据了解,Boost.python支持的C++特性比这丰富得多,这里只用到了一小部分而已。

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发 Python
相关文章 更多
C++动态数组初始化怎么写?常用语句与代码示例
C++动态数组初始化怎么写?常用语句与代码示例

深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

Python安装后怎么打开:使用IDLE或命令行启动解释器
Python安装后怎么打开:使用IDLE或命令行启动解释器

刚在Windows安装好Python却不知道如何启动?本文详细演示如何通过开始菜单找到并打开IDLE集成开发环境,以及如何在PowerShell或命令提示符中使用python和py命令启动交互式解释器、运行.py脚本文件。包含退出解释器的方法及常见启动问题排查,帮助初学者快速验证安装成功并开始编写代码。

Windows系统Python安装教程:下载、勾选PATH及环境变量配置
Windows系统Python安装教程:下载、勾选PATH及环境变量配置

针对Windows初学者的Python安装实战指南。详细讲解如何从Python官网下载匹配架构的安装包,重点演示安装首屏勾选“Add python.exe to PATH”的关键操作,并提供使用python --version和py命令验证环境变量的具体步骤,帮助新手快速搭建开发环境并排查路径问题。

麒麟OS如何查看Python进程的运行状态
麒麟OS如何查看Python进程的运行状态

要想确认麒麟OS中Python程序的运行状态以及资源占用情况,我们可以这样做:用ps -ef | grep python来筛选进程;通过top命令,按P键排序查看实时负载;使用pgrep -f "script.py"精准获取PID;借助lsof -p PID验证文件打开状态。另外,还可以结合syst

Python在Debian上如何配置SSL证书
Python在Debian上如何配置SSL证书

在Debian系统上配置SSL证书通常涉及以下几个步骤:安装Web服务器:首先,你需要一个Web服务器,比如Apache或Nginx。这里以Apache为例。sudo apt updatesudo apt install apache2获取SSL证书:你可以从Let’s Encrypt免费获取SSL

统信UOS怎么安装Python开发环境
统信UOS怎么安装Python开发环境

要想让Python项目在统信UOS上正常运行,得先安装python3、python3-pip、python3-venv、python3-dev以及build-essential等组件。具体操作就是执行sudo apt install命令来一步到位完成安装,同时别忘了配置清华镜像源来给pip加速哦。在

纯Python方案实现中英文全文搜索
纯Python方案实现中英文全文搜索

在互联网上的各类网站中,无论大小,基本上都会有一个搜索框,用来给用户对内容进行搜索,小到站点搜索,大到搜索引擎搜索。从简单的来说,搜索功能确实很简单,一个简单的select语句就可以实现数据的搜索。而从复杂的来看,无论是搜索的精度还是搜索的效率,都是有很深的研究范围的。对于简单的搜索功能来说,一个s

Mac如何取消通过Python脚本运行的关机程序
Mac如何取消通过Python脚本运行的关机程序

立即在终端输入sudo shutdown -c取消倒计时关机,成功后显示“Shutdown cancelled”;若存在pmset重复任务,需再执行sudo pmset repeat cancel清除。Mac因Python脚本执行了os.system("sudo shutdown -h +10")或

Pythonasyncio异步并发与多固定出口IP调度实战
Pythonasyncio异步并发与多固定出口IP调度实战

之前写过一篇同步场景下用 Python 管理多个固定出口 IP 的实践(ExitPool + requests/httpx),覆盖了健康检查、故障转移和连接池复用。但在实际业务中,越来越多的场景用 asyncio 做高并发采集或批量接口调用——异步事件循环下多出口的管理方式和同步场景完全不同:单线程

Python在静态出口IP产品中的实战:从地址漂移巡检到多IP故障切换
Python在静态出口IP产品中的实战:从地址漂移巡检到多IP故障切换

写在前面:为什么静态出口 IP 不是"买了就行"不少团队在引入静态出口 IP 产品时,第一反应往往是:“地址配上去,这事就算完了。”可真到了真实业务里,静态出口 IP 真正能体现价值的地方,往往不在分配这一步,而在分配之后怎么管:地址有没有漂移,质量是否达标,某一条线路突然不可用时怎么切换,连接层又

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

Copyright ©2018-2026