在Python中高效调用C++代码:pybind11实战指南
背景
存在项目需要在Python中调用C++的代码。使用C++封装一个库供Python使用。常见方案选择有:boost::python 以及 pybind11。boost::python比较麻烦,要下载boost完整库,pybind11更加易用一些。
主要方案对比
特性 | boost::python | pybind11 |
---|---|---|
依赖 | 需要安装完整的Boost库 | 仅需安装pybind11包(纯头文件) |
编译速度 | 较慢 | 较快 |
学习曲线 | 较陡峭 | 较平缓 |
社区活跃度 | 一般 | 非常活跃 |
C++标准 | C++11及以上 | C++11及以上 |
使用pybind11封装Python库
安装pybind11
pip install pybind11
创建C++绑定代码
创建一个binding.cpp
文件,内容如下:
#include <pybind11/pybind11.h>
#include <string>
namespace py = pybind11;
// C++函数示例
std::string hello() {
return "Hello World from C++!";
}
int add(int a, int b) {
return a + b;
}
// 定义Python模块
PYBIND11_MODULE(example_module, m) {
m.doc() = "简单的pybind11示例模块"; // 可选的模块文档
m.def("hello", &hello, "返回一条问候语");
m.def("add", &add, "将两个数相加",
py::arg("a") = 1, // 默认参数值
py::arg("b") = 2);
}
编译方法
方法一:使用setup.py(推荐Python开发者使用)
创建setup.py
文件:
from setuptools import setup, Extension
import pybind11
ext_modules = [
Extension(
"example_module", # 生成的模块名
["binding.cpp"], # 源文件
include_dirs=[pybind11.get_include()], # 获取pybind11头文件路径
language='c++'
),
]
setup(
name="example_module",
ext_modules=ext_modules,
install_requires=['pybind11>=2.6.0'],
setup_requires=['pybind11>=2.6.0'],
python_requires=">=3.6"
)
编译命令:
python setup.py build_ext --inplace
方法二:使用Visual Studio(使用cmake同理 实际就是生成一个动态库)(推荐C++开发者使用)
-
创建一个DLL项目
-
设置项目属性:
- 配置类型:Dynamic Library (.dll)
- 目标文件扩展名:.pyd
- C/C++ -> 常规 -> 附加包含目录:
Python安装目录\include
- 链接器 -> 常规 -> 附加库目录:
Python安装目录\libs
- 链接器 -> 输入 -> 附加依赖项:
python3X.lib # 根据您的Python版本替换X,如python310.lib
-
编译项目,生成的.pyd文件即为Python可导入的模块
在Python中使用封装的库
import example_module
# 调用C++函数
print(example_module.hello()) # 输出:Hello World from C++!
print(example_module.add(5, 7)) # 输出:12
print(example_module.add()) # 使用默认参数,输出:3
可能遇到的问题与解决方案
编译错误
-
找不到pybind11头文件
- 确保正确安装了pybind11
- 检查包含目录设置是否正确
- 常见路径:
Python安装目录\Lib\site-packages\pybind11
-
预编译头错误
- 在VS中禁用预编译头:C/C++ -> 预编译头 -> 不使用预编译头
运行时错误
-
模块导入失败
- 确保.pyd文件名与PYBIND11_MODULE中的名称一致
- 确保.pyd文件在Python的搜索路径中
-
类型转换错误
- 确保Python和C++之间的数据类型匹配
高级功能示例
暴露C++类到Python
#include <pybind11/pybind11.h>
#include <string>
namespace py = pybind11;
class Pet {
public:
Pet(const std::string &name) : name(name) { }
void setName(const std::string &name_) { name = name_; }
const std::string &getName() const { return name; }
void makeSound() {
printf("%s: 汪汪!\n", name.c_str());
}
private:
std::string name;
};
PYBIND11_MODULE(example_module, m) {
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def("setName", &Pet::setName)
.def("getName", &Pet::getName)
.def("makeSound", &Pet::makeSound);
}
Python中使用:
import example_module
# 创建宠物对象
dog = example_module.Pet("小黑")
print(dog.getName()) # 输出:小黑
dog.makeSound() # 输出:小黑: 汪汪!
dog.setName("旺财")
print(dog.getName()) # 输出:旺财
总结
pybind11提供了一种简洁、高效的方式来将C++代码封装为Python可用的模块。相比boost::python,它更加轻量级和现代化,不需要复杂的依赖和构建过程。
通过创建符合Python模块约定的.pyd文件,我们可以在保持C++高性能的同时,享受Python编程的便利性和丰富的生态系统。这对于需要同时利用两种语言优势的项目尤为重要。
无论是为了性能优化、集成现有C++库,还是开发需要底层系统访问的功能,pybind11都是一个值得掌握的工具。