三、C++ 的 python 绑定 pybind11
文章目录
- 一、pybind11 简介
- 二、实战:使用 pybind11 和 CMake 创建 Python 模块
- 第 1 步:项目结构
- 第 2 步:C++ 核心代码
- 第 3 步:编写绑定代码
- 第 4 步:配置 CMake
- 第 5 步:编译与测试
一、pybind11 简介
简介 | 一个只依赖头文件的 C++11 库,让你能用纯 C++ 代码优雅地创建绑定 |
优点 | 代码极其简洁,与现代 C++ 语法完美融合,自动处理类型转换和引用计数,与 CMake 集成极佳 |
缺点 | 仅针对 Python,需要支持 C++11 或更高版本的编译器 (小问题) |
二、实战:使用 pybind11 和 CMake 创建 Python 模块
我们的目标是:创建一个 C++ 的 Calculator 类
,并将其封装成一个 Python 模块
,让我们可以 import
并在 Python 中使用它。
第 1 步:项目结构
先构建一个结构如下的项目文件夹
calculator_project/
├── CMakeLists.txt # 主 CMake 配置文件
├── src/
│ ├── calculator.h # C++ 类的头文件
│ └── calculator.cpp # C++ 类的实现文件
├── bindings/
│ └── py_bindings.cpp # 专门用于编写绑定的 C++ 代码
└── test.py # 用于测试的 Python 脚本
第 2 步:C++ 核心代码
这是我们要暴露给 Python 的功能代码。代码位于。
// src/calculator.h
#pragma onceclass Calculator {
private:int last_result_;public:Calculator();void add(int value);void subtract(int value);int getResult() const;
};
// src/calculator.cpp
#include "calculator.h"Calculator::Calculator() : last_result_(0) {}void Calculator::add(int value) {this->last_result_ += value;
}void Calculator::subtract(int value) {this->last_result_ -= value;
}int Calculator::getResult() const {return this->last_result_;
}
第 3 步:编写绑定代码
这是连接 C++ 和 Python 的桥梁。你只需要用 .def()
来声明要 暴露哪个函数,pybind11 会自动处理参数类型转换、函数调用等所有脏活累活。
// bindings/py_bindings.cpp
#include <pybind11/pybind11.h>
#include "../src/calculator.h" // 包含我们要绑定的 C++ 头文件namespace py = pybind11;// PYBIND11_MODULE 是一个特殊的宏,它会创建 Python 模块的入口点
// 第一个参数 (my_calculator) 是 Python 中 `import` 时使用的模块名
// 第二个参数 (m) 是一个 py::module_ 对象,代表了这个模块本身
PYBIND11_MODULE(my_calculator, m) {// 可选:为模块添加文档字符串m.doc() = "A simple calculator module made with pybind11";// py::class_ 是一个模板,用于将 C++ 类暴露给 Python// 尖括号里是你要绑定的 C++ 类名py::class_<Calculator>(m, "Calculator") // 在 Python 中,这个类的名字将是 "Calculator".def(py::init<>()) // 绑定构造函数。py::init<>() 表示绑定无参构造函数.def("add", &Calculator::add, "Adds a value to the current result") // 绑定 add 方法.def("subtract", &Calculator::subtract, "Subtracts a value") // 绑定 subtract 方法.def("get_result", &Calculator::getResult, "Returns the current result"); // 绑定 getResult 方法
}
第 4 步:配置 CMake
现在我们用 CMake 把所有东西串起来。这需要你系统上安装了 Python 的开发文件,在 Ubuntu/Debian 上通常是 python3-dev
。
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(CalculatorProject)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# --- 1. 获取 pybind11 ---
# 推荐使用 FetchContent,它会自动下载并配置好 pybind11
include(FetchContent)
FetchContent_Declare(pybind11GIT_REPOSITORY https://github.com/pybind/pybind11.gitGIT_TAG v2.12.0 # 使用一个稳定版本
)
FetchContent_MakeAvailable(pybind11)# --- 2. 定义我们的 C++ 库 ---
# 把核心逻辑编译成一个静态库,方便管理
add_library(calculator_lib STATIC src/calculator.cpp)
# 确保头文件路径被包含
target_include_directories(calculator_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)# --- 3. 创建 Python 模块 ---
# pybind11_add_module 是 pybind11 提供的 CMake 函数,专门用于创建 Python 扩展模块
# 它会生成一个 .so (Linux) 或 .pyd (Windows) 文件
pybind11_add_module(my_calculator MODULE bindings/py_bindings.cpp)# --- 4. 链接依赖 ---
# 告诉我们的 Python 模块,它需要链接到 calculator_lib 的代码
target_link_libraries(my_calculator PRIVATE calculator_lib)
第 5 步:编译与测试
现在,标准的 CMake 构建流程即可:
# 1. 创建构建目录并进入
mkdir build
cd build# 2. 运行 CMake 来配置项目
cmake ..# 3. 编译
cmake --build . --config Release
编译成功后,在 build
目录下,你会找到一个名字类似 my_calculator.cpython-310-x86_64-linux-gnu.so
的文件。这就是我们用 C++ 创建的、货真价实的 Python 模块!
现在,来测试一下。
# 确保你的 python 环境可以找到 build 目录下的模块
# 或者直接在 build 目录下运行 python
import sys
# sys.path.append('./build') # 如果不在 build 目录运行,添加此行try:# 导入我们用 C++ 写的模块!import my_calculatorprint("Successfully imported 'my_calculator'")
except ImportError as e:print(f"Failed to import module: {e}")sys.exit(1)# 创建 Calculator 类的实例,就像它是一个普通的 Python 类
calc = my_calculator.Calculator()
print(f"Initial result: {calc.get_result()}")# 调用它的方法
calc.add(20)
calc.add(5)
print(f"After adding 20 and 5, result is: {calc.get_result()}")calc.subtract(10)
print(f"After subtracting 10, result is: {calc.get_result()}")# 查看文档字符串
print("\nModule docstring:")
print(my_calculator.__doc__)
print("\nClass docstring (default):")
print(my_calculator.Calculator.__doc__)
运行测试:
# 在项目根目录运行
cd build
python ../test.py
输出: