找回密码
 会员注册
查看: 34|回复: 0

C++和Python混合编程之Pybind11的简单使用

[复制链接]

6

主题

0

回帖

19

积分

新手上路

积分
19
发表于 2024-9-3 11:14:41 | 显示全部楼层 |阅读模式
C++和Python混合编程之Pybind11的简单使用一、简介Pybind11是C++/Python混合编程的利器之一,是一个轻量级的只包含头文件的库,用于Python和C++之间接口转换,可以为现有的C++代码创建Python接口绑定。Pybind11名字里的“11”表示它完全基于现代C++开发(C++11以上),所以没有兼容旧系统的负担。它使用了大量的现代C++特性,不仅代码干净整齐,运行效率也更高。二、平台环境1、系统:Windows102、Python虚拟环境工具:Anaconda33、C++IDE:VisualStudio20224、Python版本:3.7.16三、C++/Python相互调用的方法简单介绍如何实现两种语言之间相互调用1、Python调用C++代码:通过调用动态库的方式完成,将C++代码编译生成动态库文件(Win下为.DLL),Python调用的话需要将库后缀改为(.pyd),然后将动态库拷贝到Python文件主目录,代码内导入库模块即可;2、C++代码调用Python:主要通过调用Python代码解释器来实现。四、代码实践用代码实例简单展现Pybind11的功能1、基础环境搭建1.1、安装Pybind11库有多种安装方式,这里通过pip命令来安装,如果使用了虚拟环境,安装前记得激活相应的虚拟环境:安装命令如下:(py37)C:\Users\xxx>pipinstallpybind1111.2、VisualStudio项目属性配置:具体路径根据自己项目实际情况而定1).通用编译属性设置:属性–>常规–>常规属性–>配置类型:动态库(.dll);属性–>高级–>高级属性–>目标文件扩展名:.pyd;2).C/C++附加包含目录include:属性–>C/C++-->常规–>附加包含目录:D:\Anaconda3\envs\py37\includeD:\Anaconda3\envs\py37\Lib\site-packages\pybind11\include3).链接器附加库目录和库文件:属性–>链接器–>常规–>附加库目录:D:\Anaconda3\envs\py37\libs属性–>链接器–>输入–>附加依赖项:python3.lib,python37.lib具体操作如下:1.3、系统环境变量设置1)、因为在C++调用Python代码过程中遇到错误,经过查资料找到了解决办法(stackoverflow讨论地址),以下环境具体路径根据自己项目实际情况而定。2)、要在C++中调用Python解释器(py::scoped_interpreterguard{};),需要添加两个系统环境变量,以便Pybind11能够找到解释器位置:PYTHONHOME:D:\Anaconda3\envs\py37PYTHONPATH:D:\Anaconda3\envs\py37\Lib;D:\Anaconda3\envs\py37\Lib\site-packages;D:\Anaconda3\envs\py37\DLLs3)、如果不设置这两个环境变量会出现以下错误:FatalPythonerror:init_fs_encoding:failedtogetthePythoncodecofthefilesystemencodingPythonruntimestate:coreinitializedModuleNotFoundError:Nomodulenamed‘encodings’4)、设置完后重启电脑生效从打印的错误可以看出[PYTHONHOME,PYTHONPATH]两个系统环境变量未设置。设置系统变量:注意:增加这两个环境变量后可能导致Anaconda3虚拟环境命令找不到,进而无法激活虚拟环境,如果出现则删除这两个环境变量即可(暂时没找到好的解决办法),删除后重启电脑。2、Python使用C++代码动态库演示两个流程:C++编译动态库;Python代码中调用动态库。2.1、C++编译动态库演示C++编译动态库以供Python调用代码示例:#include#include#include#include#include#include #include //转换标准容器必须的头文件namespacepy=pybind11;//名字空间别名,简化代码classPointfinal{private: intx=0;public: Point()=default; ~Point()=default; Point(inta):x(a){}public: intget()const { returnx; } voidset(inta) { x=a; }};//用lambda表达式来测试PYBIND11_MODULE(pydemo,m)//定义Python模块pydemo{ m.doc()="pybind11demodoc"; m.def("info", []() { py::print("c++version:",__cplusplus); } ); m.def("add", [](inta,intb) { returna+b; } ); m.def("use_str", [](conststd::string&str)//定义python函数,入参是string { py::print(str); returnstr+"!!";//返回string } ); m.def("use_tuple", [](std::tuplex)//定义python函数,入参是tuple { std::get(x)++; std::get(x)++; std::get(x)+="??"; returnx; } ); m.def("use_list", [](std::vector&v)//定义python函数,入参是vector { autovv=v; py::print("input:",vv); vv.push_back(100); vv.push_back(200); returnvv; } ); m.def("use_map", [](std::map&m)//定义python函数,入参是map { automm=m; py::print("input:",mm); mm["name"]="LiMing"; mm["gender"]="male"; returnmm; } ); //C++里的类也能够等价地转换到Python里面调用,这要用到一个特别的模板类class_ py::class_ (m,"Point")//定义Python类 .def(py::init())//导出构造函数 .def(py::init())//导出构造函数 .def("get",&oint::get)//导出成员函数 .def("set",&oint::set)//导出成员函数 ;}#if0//用普通函数来测试voidinfo(){ std::cout<< "c++ version: " << __cplusplus << std::endl; } int add(int a, int b) { return a + b; } PYBIND11_MODULE(pydemo, m) { m.doc() = "pybind11 demodoc"; m.def("info", &info, "cpp info"); m.def("add", &add, "add func"); } #endif #if 0 int main() { return 0; } #endif 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 编译输出结果如下: 生成开始于 13:12... 1>------已启动生成:项目ythonAndCPP,配置:Releasex64------1>main.cpp1>正在创建库F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.lib和对象F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.exp1>正在生成代码1>441of3091functions(14.3%)werecompiled,therestwerecopiedfrompreviouscompilation.1>186functionswerenewincurrentcompilation1>391functionshadinlinedecisionre-evaluatedbutremainunchanged1>已完成代码的生成1>ythonAndCPP.vcxproj->F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.pyd==========生成:1成功,0失败,0最新,0已跳过====================生成于13:12完成,耗时03.362秒==========1234567891011122.2、在Python中调用动态库首先需要将动态库拷贝到Python项目主目录下,然后才能在python代码中导入模块使用首先需要将动态库拷贝到Python项目主目录下,如下图所示:代码示例:importpydemo#导入模块,与C++代码中定义的模块名一致deftest():pydemo.info() //调用add函数print("Testaddfunc:")print(pydemo.add(1,2)) //调用Point类极其成员函数print("TestPointclass:")p=pydemo.Point(10)print(p.get())p.set(88)print(p.get()) //字符串转换测试:std::string->strprint("Teststr:")print(pydemo.use_str("hello")) //元组转换测试:std::tuple->tupleprint("Testtuple:")t=(11,22,"No")print(pydemo.use_tuple(t)) //列表转换测试:std::vector->listprint("Testlist:")l=[]print(pydemo.use_list(l)) //键值对转换测试:std::map->mapprint("Testmap:")m={}print(pydemo.use_map(m))defmain():test()if__name__=="__main__":main()12345678910111213141516171819202122232425262728293031323334353637383940414243运行结果如下:(py37)F:\code\pydemo\Test>pythonmain.pyc++version:199711Testaddfunc:3TestPointclass:1088Teststr:hellohello!!Testtuple12,23,'No??')Testlist:input:[][100,200]Testmap:input:{}{'gender':'male','name':'LiMing'}1234567891011121314151617183、C++调用Python解释器用两种方式演示如何在C++代码中调用Python代码解释器:直接运行python代码导入python外部模块执行代码,更灵活注意细节:需要将python代码文件拷贝至C++项目主目录下;将所用python版本的python3.dll,python37.dll两个动态库拷贝至C++可执行文件(.exe)所在目录,否则可能无法运行或运行出错。3.1拷贝代码运行所需文件1)、拷贝python代码文件2)、拷贝DLL文件3.2、代码演示代码示例:python代码文件:pydemo.pypython代码:importosfromtypingimportList,AnyStrdefget_files(path:str)->List[AnyStr]:"""遍历目录下所有文件并返回结果:parampath:目录:return:返回文件列表"""ifnotos.path.exists(path):return[]#递归遍历文件夹下的所有文件files=[]for(dirpath,dirnames,filenames)inos.walk(path):files+=filenamesreturnfilesif__name__=="__main__":pass123456789101112131415161718192021222324CPP代码:#include#include#include#include #include #include //要用解释器需要包含此头文件namespacepy=pybind11;//名字空间别名,简化代码#if1voidtest_pybind11(){ py::scoped_interpreterguard{};//初始化Python解释器 //1、直接运行python代码 std::cout<< "1、测试直接运行python代码:" << std::endl; try { // 使用原始字符串R"()" py::exec(R"( def pow(a,n): return a**n)"); auto func = py::module::import("__main__").attr("pow"); auto res = func(2, 3).cast(); std::cout<< "pow(2,3)函数输出结果如下:" << std::endl; std::cout << res << std::endl; } catch (const std::exception& e) { std::cout << e.what() << std::endl; } std::cout << std::endl; // 2、导入python模块执行代码(pydemo.py),此种方法更灵活 std::cout << "2、测试导入python模块执行外部代码:" << std::endl; try { auto module = py::module::import("pydemo"); // 导入python外部模块pydemo,python中一个.py文件就是一个模块 auto res = module.attr("get_files")("C:\\Users\\xxx\\Pictures\\wallpaper"); std::cout << "遍历文件如下: " << std::endl; for (const auto& val : res) { std::cout << val << std::endl; } std::cout << std::endl; } catch (const std::exception& e) { std::cout << e.what() << std::endl; } } #endif #if 1 int main() { test_pybind11(); return 0; } #endif 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 运行结果如下: 1、测试直接运行python代码: pow(2,3)函数输出结果如下: 8 2、测试导入python模块执行外部代码: 遍历文件如下: img_1.png img_10.jpg img_11.jpg img_12.jpg img_13.png img_14.jpg F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.pyd (进程 17896)已退出,代码为 0。 按任意键关闭此窗口. . . 123456789101112131415 五、结语 本文简单阐述了用Pybind11实现C++/Python混合编程的流程和注意事项,仅使用了Pybind11的一些简单功能,Pybind11功能很强大,需要其它更多功能请自行查询Pybind11官方文档。 【参考文章】: 极客时间专栏-罗剑锋的 C++ 实战笔记之混合系统章节Python/C++混合编程利器Pybind11实践Pybind11官方文档
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 23:29 , Processed in 0.991207 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表