首页 > Python资料 博客日记
C++和Python混合编程之Pybind11的简单使用
2024-07-18 12:00:05Python资料围观376次
C++和Python混合编程之Pybind11的简单使用
一、简介
Pybind11是C++/Python混合编程的利器之一,是一个轻量级的只包含头文件的库,用于 Python 和 C++ 之间接口转换,可以为现有的 C++ 代码创建 Python 接口绑定。Pybind11 名字里的“11”表示它完全基于现代 C++ 开发(C++11 以上),所以没有兼容旧系统的负担。它使用了大量的现代 C++ 特性,不仅代码干净整齐,运行效率也更高。
二、平台环境
1、系统:Windows10
2、Python虚拟环境工具:Anaconda3
3、C++ IDE:Visual Studio 2022
4、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> pip install pybind11
1.2、Visual Studio项目属性配置:
具体路径根据自己项目实际情况而定
1). 通用编译属性设置:
- 属性–>常规–>常规属性–>配置类型:动态库(.dll);
- 属性–>高级–>高级属性–>目标文件扩展名:.pyd;
2). C/C++附加包含目录include:
- 属性–>C/C++ -->常规–>附加包含目录:
- D:\Anaconda3\envs\py37\include
- D:\Anaconda3\envs\py37\Lib\site-packages\pybind11\include
3). 链接器附加库目录和库文件:
- 属性–>链接器–>常规–>附加库目录:D:\Anaconda3\envs\py37\libs
- 属性–>链接器–>输入–>附加依赖项:python3.lib,python37.lib
具体操作如下:
1.3、系统环境变量设置
1)、因为在C++调用Python代码过程中遇到错误,经过查资料找到了解决办法(stackoverflow讨论地址),以下环境具体路径根据自己项目实际情况而定。
2)、要在C++中调用Python解释器(py::scoped_interpreter guard{};),需要添加两个系统环境变量,以便Pybind11能够找到解释器位置:
- PYTHONHOME:D:\Anaconda3\envs\py37
- PYTHONPATH:D:\Anaconda3\envs\py37\Lib;D:\Anaconda3\envs\py37\Lib\site-packages;D:\Anaconda3\envs\py37\DLLs
3)、如果不设置这两个环境变量会出现以下错误:
- Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named ‘encodings’4)、设置完后重启电脑生效
从打印的错误可以看出[PYTHONHOME,PYTHONPATH]两个系统环境变量未设置。
设置系统变量:
注意: 增加这两个环境变量后可能导致Anaconda3虚拟环境命令找不到,进而无法激活虚拟环境,如果出现则删除这两个环境变量即可(暂时没找到好的解决办法),删除后重启电脑。
2、Python使用C++代码动态库
演示两个流程:
- C++编译动态库;
- Python代码中调用动态库。
2.1、C++编译动态库
演示C++编译动态库以供Python调用
代码示例:
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <map>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // 转换标准容器必须的头文件
namespace py = pybind11; // 名字空间别名,简化代码
class Point final
{
private:
int x = 0;
public:
Point() = default;
~Point() = default;
Point(int a) : x(a) {}
public:
int get() const
{
return x;
}
void set(int a)
{
x = a;
}
};
// 用lambda表达式来测试
PYBIND11_MODULE(pydemo, m) // 定义Python模块pydemo
{
m.doc() = "pybind11 demo doc";
m.def("info",
[]()
{
py::print("c++ version: ", __cplusplus);
}
);
m.def("add",
[](int a, int b)
{
return a + b;
}
);
m.def("use_str",
[](const std::string& str) // 定义python函数,入参是string
{
py::print(str);
return str + "!!"; // 返回string
}
);
m.def("use_tuple",
[](std::tuple<int, int, std::string> x) // 定义python函数,入参是tuple
{
std::get<0>(x)++;
std::get<1>(x)++;
std::get<2>(x) += "??";
return x;
}
);
m.def("use_list",
[](std::vector<int>& v) // 定义python函数,入参是vector
{
auto vv = v;
py::print("input :", vv);
vv.push_back(100);
vv.push_back(200);
return vv;
}
);
m.def("use_map",
[](std::map<std::string, std::string>& m) // 定义python函数,入参是map
{
auto mm = m;
py::print("input : ", mm);
mm["name"] = "LiMing";
mm["gender"] = "male";
return mm;
}
);
// C++ 里的类也能够等价地转换到 Python 里面调用,这要用到一个特别的模板类 class_
py::class_<Point>(m, "Point") // 定义Python类
.def(py::init()) // 导出构造函数
.def(py::init<int>()) // 导出构造函数
.def("get", &Point::get) // 导出成员函数
.def("set", &Point::set) // 导出成员函数
;
}
#if 0
// 用普通函数来测试
void info()
{
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
编译输出结果如下:
生成开始于 13:12...
1>------ 已启动生成: 项目: PythonAndCPP, 配置: Release x64 ------
1>main.cpp
1> 正在创建库 F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.lib 和对象 F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.exp
1>正在生成代码
1>441 of 3091 functions (14.3%) were compiled, the rest were copied from previous compilation.
1> 186 functions were new in current compilation
1> 391 functions had inline decision re-evaluated but remain unchanged
1>已完成代码的生成
1>PythonAndCPP.vcxproj -> F:\code\CPPdemo\PythonAndCPP\x64\Release\PythonAndCPP.pyd
========== 生成: 1 成功,0 失败,0 最新,0 已跳过 ==========
========== 生成 于 13:12 完成,耗时 03.362 秒 ==========
2.2、在Python中调用动态库
首先需要将动态库拷贝到Python项目主目录下,然后才能在python代码中导入模块使用
首先需要将动态库拷贝到Python项目主目录下,如下图所示:
代码示例:
import pydemo # 导入模块,与C++代码中定义的模块名一致
def test():
pydemo.info()
// 调用add函数
print("Test add func: ")
print(pydemo.add(1,2))
// 调用Point类极其成员函数
print("Test Point class: ")
p = pydemo.Point(10)
print(p.get())
p.set(88)
print(p.get())
// 字符串转换测试:std::string->str
print("Test str: ")
print(pydemo.use_str("hello"))
// 元组转换测试:std::tuple->tuple
print("Test tuple: ")
t = (11,22,"No")
print(pydemo.use_tuple(t))
// 列表转换测试:std::vector->list
print("Test list: ")
l = []
print(pydemo.use_list(l))
// 键值对转换测试:std::map->map
print("Test map: ")
m = {}
print(pydemo.use_map(m))
def main():
test()
if __name__ =="__main__":
main()
运行结果如下:
(py37) F:\code\pydemo\Test>python main.py
c++ version: 199711
Test add func:
3
Test Point class:
10
88
Test str:
hello
hello!!
Test tuple:
(12, 23, 'No??')
Test list:
input : []
[100, 200]
Test map:
input : {}
{'gender': 'male', 'name': 'LiMing'}
3、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.py
python代码:
import os
from typing import List, AnyStr
def get_files(path:str) -> List[AnyStr]:
"""
遍历目录下所有文件并返回结果
:param path: 目录
:return: 返回文件列表
"""
if not os.path.exists(path):
return []
# 递归遍历文件夹下的所有文件
files = []
for (dirpath, dirnames, filenames) in os.walk(path):
files += filenames
return files
if __name__ == "__main__":
pass
CPP代码:
#include <iostream>
#include <string>
#include <vector>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/embed.h> // 要用解释器需要包含此头文件
namespace py = pybind11; // 名字空间别名,简化代码
#if 1
void test_pybind11()
{
py::scoped_interpreter guard{}; // 初始化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<int>();
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
运行结果如下:
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。
按任意键关闭此窗口. . .
五、结语
本文简单阐述了用Pybind11实现C++/Python混合编程的流程和注意事项,仅使用了Pybind11的一些简单功能,Pybind11功能很强大,需要其它更多功能请自行查询Pybind11官方文档。
【参考文章】:
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Anaconda版本和Python版本对应关系(持续更新...)
- Python与PyTorch的版本对应
- Windows上安装 Python 环境并配置环境变量 (超详细教程)
- Python pyinstaller打包exe最完整教程