当前位置:

首页 > 编程开发 > 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才是两个具体的类型;
    printA、addA、accumulate是三个导出的函数。即使参数是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++特性比这丰富得多,这里只用到了一小部分而已。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Python
    相关文章 更多
    python删除一个列表里的重复元素的方法与代码示例
    python删除一个列表里的重复元素的方法与代码示例

    学习如何使用Python高效删除列表中的重复元素。本文提供5种常见方法的代码示例,涵盖保持顺序与不保持顺序的场景,适合初学者和进阶开发者参考。

    Python写接口的步骤及完整代码示例
    Python写接口的步骤及完整代码示例

    详细讲解使用Python编写Web接口的核心步骤与完整代码示例,涵盖环境准备、路由定义、数据校验、服务启动及调试测试方法。

    codex怎么读取word文档及docx文件操作教程
    codex怎么读取word文档及docx文件操作教程

    介绍如何利用编程工具读取和解析Word文档(docx格式),涵盖基础文本提取代码示例、结构化数据处理逻辑,以及处理复杂排版时的技术局限与替代方案。

    怎么在Python中调用C语言代码提升性能
    怎么在Python中调用C语言代码提升性能

    详细讲解在Python中调用C语言代码提升性能的完整实践指南,包含原理分析、编译步骤、ctypes与CFFI具体实现、性能对比及常见陷阱。

    Python与C语言的性能差异及底层原理
    Python与C语言的性能差异及底层原理

    深入解析Python与C语言在执行效率上的数量级差异,从编译机制、变量内存布局、动态类型推断到GIL锁,系统阐明底层核心原理。

    Python和C语言哪个更适合编程入门
    Python和C语言哪个更适合编程入门

    客观对比Python与C语言在心智负担、底层机制与学习反馈上的核心差异,提供清晰有据的选择标准与切实的入门学习路径。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    langgraph python guide中文入门教程详解
    langgraph python guide中文入门教程详解

    本篇LangGraph Python中文入门教程详解,系统梳理StateGraph状态机定义、节点编写、边与条件路由、检查点持久化(MemorySaver)等核心机制,提供从环境安装到完整循环智能体构建的代码示例与实操指导。

    Windows 安装 Python:从官方入口到虚拟环境的可复现实践
    Windows 安装 Python:从官方入口到虚拟环境的可复现实践

    面向后端开发者,基于 Python 官方文档说明如何在 Windows 上安装 Python、创建 venv 虚拟环境及使用 pip 管理依赖。包含可复制命令、验证方法及工具选择边界,避免常见配置误区。

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

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

    Windows
    Windows

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

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    为什么要购买正版软件?
    01 官方授权

    授权来源透明,订单与许可信息可随时查询。

    02 即时交付

    付款成功后自动发放激活信息,支持订单追踪。

    03 中文协助

    安装、激活、迁移与续费问题都有专人响应。

    04 长期可用

    提供升级提醒与授权管理,换机时也能轻松迁移。

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

    Copyright ©2018-2026