pybind11 是python项目中最常用的工具,这里做个介绍。
一、原理
pybind11 是一个轻量级的 C++ 库,用于将 C++ 代码暴露给 Python 调用(反之亦可)。它的核心原理可以概括为:利用 C++ 的编译时元编程(特别是模板元编程)和编译器的特性,自动生成 CPython API 的胶水代码。
-
基于 CPython API:
pybind11 的底层仍然是 CPython 的 API(如PyObject,PyModule_Create,PyType_Ready等)。但它将这些复杂、易错且冗长的 C API 调用封装成了高度抽象、类型安全的 C++ 接口。 -
模板元编程
这是 pybind11 的魔法之源。通过精巧的模板设计,它能够在编译期推导类型信息并生成对应的转换代码。-
类型转换 当你写
m.def("add", &add);时,pybind11 的模板会推导出add函数的参数和返回类型(例如int(int, int))。然后,它会在编译期生成相应的代码:从 Python 的PyObject*(如int) 中提取出 C++ 的int,调用你的函数,再将 C++ 的int返回值转换回 Python 的PyObject*。 -
多态与动态类型 通过
py::class_<Animal>和py::class_<Dog, Animal>这样的声明,pybind11 利用模板在 C++ 侧构建了一套继承体系,并能正确地在 Python 侧处理isinstance()和继承关系。
-
-
RAII 和智能指针
pybind11 大量使用 RAII (Resource Acquisition Is Initialization) 来管理资源(如引用计数)。它完美地集成了std::unique_ptr和std::shared_ptr,可以自动在 C++ 和 Python 之间传递对象所有权,防止内存泄漏。Python 对象的生命周期由引用计数管理,而pybind11::handle等类自动处理Py_INCREF和Py_DECREF。 -
编译时计算
很多工作都在编译期完成,而非运行时。这意味着生成的二进制代码是高度优化的,运行时开销极小。最终的扩展模块就是一个原生的.so(Linux) 或.pyd(Windows) 文件,性能与手写 C 扩展无异。
与传统方法(如手写 CPython API 或 Boost.Python)的对比:
vs 手写 CPython API pybind11 代码更简洁、安全、易维护,无需手动处理繁琐的引用计数和类型检查。
vs Boost.Python pybind11 是 header-only 的,依赖极轻(仅需 Python 和 C++ 标准库),编译更快,并且利用了 C++11 及以后的新特性,语法更现代。
二、源码编译安装
pybind11 是 header-only 的,但为了更方便地构建(尤其是与 CMake 集成),推荐进行“安装”。有两种主要方式:
方法一:使用包管理器(最简单)
# Ubuntu/Debian
sudo apt-get install pybind11-dev
# Fedora
sudo dnf install pybind11-devel
# macOS (Homebrew)
brew install pybind11
安装后,头文件通常在 /usr/include 或 /usr/local/include,CMake 配置模块在相应的目录下。
方法二:从源码编译安装
-
获取源码:
git clone https://github.com/pybind/pybind11.git cd pybind11 -
创建构建目录并配置(推荐使用 out-of-source build):
mkdir build && cd build # 默认安装到 /usr/local cmake .. # 或者安装到自定义目录 cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/your/install -
编译和安装:
make -j4 # 并行编译,数字根据你的CPU核心数调整 sudo make install # 如果安装到系统目录需要sudo安装完成后,在你的项目中就可以通过
find_package(pybind11 REQUIRED)来使用它了。
三、使用方法(CMake 集成)
这是最推荐的使用方式。假设你的项目结构如下:
your_project/
├── CMakeLists.txt
├── example.cpp
└── example.py
CMakeLists.txt 是关键:
cmake_minimum_required(VERSION 3.4...3.22)
project(example)
# 1. 寻找 pybind11 包
# 如果你用方法二安装到了自定义路径,可能需要设置 CMAKE_PREFIX_PATH
find_package(pybind11 REQUIRED)
# 2. 添加你的模块
add_library(example MODULE example.cpp) # 注意是 MODULE,不是 SHARED LIBRARY
# 3. 链接 pybind11 并设置输出属性
target_link_libraries(example PRIVATE pybind11::module)
# 4. 解决不同系统下的输出文件名问题
set_target_properties(example PROPERTIES
PREFIX ""
# Linux/MacOS 输出为 example.so, Windows 输出为 example.pyd
SUFFIX "$<IF:$<PLATFORM_ID:Windows>,.pyd,.so>"
)
然后使用标准 CMake 流程编译:
mkdir build && cd build
cmake ..
make
成功后会在 build 目录生成 example.cpython-39-x86_64-linux-gnu.so 之类的文件。
四、使用示例
示例 1:基本函数绑定 (example.cpp)
#include <pybind11/pybind11.h>
int add(int i, int j) {
return i + j;
}
namespace py = pybind11;
// PYBIND11_MODULE 是宏,第一个参数是模块名(必须和文件名一致),第二个是模块句柄
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // 可选的模块说明字符串
// 定义一个函数
m.def("add", &add, "A function which adds two numbers");
// 定义一个变量
m.attr("the_answer") = 42;
}
编译后,在 Python 中:
import example
print(example.add(1, 2)) # 输出 3
print(example.the_answer) # 输出 42
示例 2:类绑定
#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_; }
static std::string getClass() { return "A Pet"; } // 静态函数
private:
std::string name_;
};
PYBIND11_MODULE(example, m) {
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>()) // 构造函数
.def("setName", &Pet::setName)
.def("getName", &Pet::getName)
.def_static("getClass", &Pet::getClass) // 绑定静态函数
.def_property_readonly("name", &Pet::getName); // 将 getName 暴露为只读属性
}
在 Python 中:
import example
p = example.Pet("Molly")
print(p.getName()) # 输出 "Molly"
p.setName("Charly")
print(p.name) # 输出 "Charly" (使用了属性)
print(p.getClass()) # 错误,getClass 是静态方法,需用类调用
print(example.Pet.getClass()) # 输出 "A Pet"
示例 3:STL 容器的自动转换
pybind11 可以自动转换 std::vector, std::list, std::map 等常见容器。
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // 必须包含这个头文件以实现自动转换
#include <vector>
#include <string>
namespace py = pybind11;
std::vector<std::string> get_words() {
return {"Hello", "World", "from", "pybind11"};
}
PYBIND11_MODULE(example, m) {
m.def("get_words", &get_words);
}
在 Python 中:
import example
words = example.get_words()
print(words) # 输出 ['Hello', 'World', 'from', 'pybind11']
print(type(words)) # 输出 <class 'list'>
五、总结
| 特性 | 说明 |
|---|---|
| 核心原理 | 利用 C++ 模板元编程在编译期自动生成 CPython API 胶水代码。 |
| 优点 | 代码简洁、类型安全、高性能、轻量级(header-only)、完美支持现代 C++。 |
| 安装 | 推荐使用系统包管理器或从源码 CMake 安装。 |
| 构建 | 强烈推荐与 CMake 集成,使用 find_package 和 target_link_libraries。 |
| 功能 | 支持函数、类、继承、STL 容器、智能指针、NumPy 数组(需包含 pybind11/numpy.h)等。 |
pybind11 极大地降低了创建 Python C++ 扩展的门槛,是连接 C++ 高性能世界和 Python 高效生态系统的首选工具。

4298

被折叠的 条评论
为什么被折叠?



