pybind11 简介

pybind11 是python项目中最常用的工具,这里做个介绍。

 

一、原理

pybind11 是一个轻量级的 C++ 库,用于将 C++ 代码暴露给 Python 调用(反之亦可)。它的核心原理可以概括为:利用 C++ 的编译时元编程(特别是模板元编程)和编译器的特性,自动生成 CPython API 的胶水代码

  1. 基于 CPython API
    pybind11 的底层仍然是 CPython 的 API(如 PyObjectPyModule_CreatePyType_Ready 等)。但它将这些复杂、易错且冗长的 C API 调用封装成了高度抽象、类型安全的 C++ 接口。

  2. 模板元编程
    这是 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() 和继承关系。

  3. RAII 和智能指针
    pybind11 大量使用 RAII (Resource Acquisition Is Initialization) 来管理资源(如引用计数)。它完美地集成了 std::unique_ptr 和 std::shared_ptr,可以自动在 C++ 和 Python 之间传递对象所有权,防止内存泄漏。Python 对象的生命周期由引用计数管理,而 pybind11::handle 等类自动处理 Py_INCREF 和 Py_DECREF

  4. 编译时计算
    很多工作都在编译期完成,而非运行时。这意味着生成的二进制代码是高度优化的,运行时开销极小。最终的扩展模块就是一个原生的 .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 配置模块在相应的目录下。

方法二:从源码编译安装
  1. 获取源码

    git clone https://github.com/pybind/pybind11.git
    cd pybind11
  2. 创建构建目录并配置(推荐使用 out-of-source build):

    mkdir build && cd build
    # 默认安装到 /usr/local
    cmake ..
    # 或者安装到自定义目录
    cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/your/install
  3. 编译和安装

    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::vectorstd::liststd::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 高效生态系统的首选工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值