pybind11:一个跨C++和Python平台的编程库

目录

1.简介

2.安装与集成

[2.1.CMake 工程(推荐,跨平台通用)](#2.1.CMake 工程(推荐,跨平台通用))

[2.2.setuptools 打包(Python 生态友好)](#2.2.setuptools 打包(Python 生态友好))

3.详细使用

3.1.基础:模块与函数绑定

3.1.1.最小模块定义

3.1.2.参数命名与默认参数

3.1.3.函数重载

[3.1.4.Lambda 表达式](#3.1.4.Lambda 表达式)

3.2.面向对象:类绑定

3.2.1.普通类绑定

3.2.2.类继承

[3.2.3.虚函数与多态(Trampoline 类)](#3.2.3.虚函数与多态(Trampoline 类))

3.3.数据类型自动转换

3.3.1.内置自动转换类型

[3.3.2.STL 容器转换](#3.3.2.STL 容器转换)

3.3.3.枚举类型

[3.4.NumPy 数组交互(核心高频场景)](#3.4.NumPy 数组交互(核心高频场景))

3.5.高级特性

[3.5.1.GIL 全局解释器锁管理](#3.5.1.GIL 全局解释器锁管理)

3.5.2.智能指针

[3.5.3.回调函数(Python 函数传入 C++)](#3.5.3.回调函数(Python 函数传入 C++))

3.5.4.异常处理

4.常见问题

[5. 和其他方案对比](#5. 和其他方案对比)


1.简介

pybind11 是一个轻量级、仅头文件 (header-only) 的 C++11 及以上库,用来把 C++ 代码封装成 Python 可直接 import 的扩展模块,实现 C++ ↔ Python 互操作,替代笨重的 Boost.Python。

核心定位:把高性能 C++ 逻辑暴露给 Python 调用;也可以在 C++ 代码里调用 Python 对象。

特点:

  1. header-only:只需要头文件,不用单独编译链接库
  2. C++11 起步,支持类、虚函数、继承、智能指针、STL 容器、枚举
  3. 自动类型转换,开销很小,接近原生 C++ 性能
  4. 跨平台:Windows/Linux/macOS;支持 CPython、PyPy
  5. 主流构建方式:CMake、setuptools、scikit-build、cppimport

2.安装与集成

2.1.CMake 工程(推荐,跨平台通用)

安装 pybind11:

cpp 复制代码
pip install pybind11
# 获取头文件路径
python -m pybind11 --includes

编写 CMakeLists.txt:

cpp 复制代码
cmake_minimum_required(VERSION 3.18)
project(example)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找pybind11(通过pip安装的会自动被找到)
find_package(pybind11 CONFIG REQUIRED)

# 生成扩展模块
pybind11_add_module(example example.cpp)

编译:

cpp 复制代码
mkdir build && cd build
cmake ..
cmake --build . --config Release

编译产物:Linux 下为 example.cpython-xxx.so,Windows 下为 example.pyd,放在 Python 脚本同目录即可导入。

2.2.setuptools 打包(Python 生态友好)

编写 setup.py,支持 pip install 一键安装:

cpp 复制代码
from setuptools import setup
from pybind11.setup_helpers import Pybind11Extension, build_ext

ext_modules = [
    Pybind11Extension(
        "example",
        ["example.cpp"],
        cxx_std=17,
    ),
]

setup(
    name="example",
    version="0.1.0",
    ext_modules=ext_modules,
    cmdclass={"build_ext": build_ext},
)

编译并本地安装:

cpp 复制代码
pip install .
# 或者原地编译生成模块
python setup.py build_ext --inplace

3.详细使用

3.1.基础:模块与函数绑定

3.1.1.最小模块定义

所有绑定代码都写在 PYBIND11_MODULE 宏中,第一个参数是模块名(Python 导入时的名字),第二个参数是模块对象 m。

cpp 复制代码
#include <pybind11/pybind11.h>
namespace py = pybind11;

// 普通C++函数
int add(int a, int b) {
    return a + b;
}

// 模块入口
PYBIND11_MODULE(example, m) {
    // 模块文档字符串
    m.doc() = "这是一个pybind11示例模块";

    // 绑定函数
    m.def("add", &add, "计算两个整数的和");
}

Python 端使用:

cpp 复制代码
import example
print(example.add(1, 2))  # 输出 3

3.1.2.参数命名与默认参数

使用 py::arg 给参数命名,支持 Python 关键字传参;同时可以指定默认值。

cpp 复制代码
m.def("add", &add,
    "计算两数之和",
    py::arg("a") = 10,  // 参数名 + 默认值
    py::arg("b") = 20
);

Python 端调用方式更灵活:

cpp 复制代码
example.add()          # 30,使用默认值
example.add(5)         # 25
example.add(b=100)     # 110,关键字传参

3.1.3.函数重载

C++ 函数重载时,必须通过 static_cast 明确指定函数指针类型,避免编译器歧义。

cpp 复制代码
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }

PYBIND11_MODULE(example, m) {
    m.def("add", static_cast<int(*)(int, int)>(&add), py::arg("a"), py::arg("b"));
    m.def("add", static_cast<double(*)(double, double)>(&add), py::arg("a"), py::arg("b"));
}

3.1.4.Lambda 表达式

可以直接绑定 lambda,适合做简单的逻辑包装或参数适配。

cpp 复制代码
m.def("greet", [](const std::string& name) {
    return "Hello, " + name + "!";
}, py::arg("name"));

3.2.面向对象:类绑定

3.2.1.普通类绑定

使用 py::class_<类名> 绑定类,支持构造函数、成员函数、静态函数、成员属性。

cpp 复制代码
#include <string>
#include <pybind11/pybind11.h>
namespace py = pybind11;

class Pet {
public:
    std::string name;

    Pet(const std::string& name) : name(name) {}

    void set_age(int age) { m_age = age; }
    int get_age() const { return m_age; }

    static std::string species() { return "dog"; }

private:
    int m_age = 0;
};

PYBIND11_MODULE(example, m) {
    py::class_<Pet>(m, "Pet")
        // 绑定构造函数
        .def(py::init<const std::string&>(), py::arg("name"))

        // 直接绑定公有成员变量(读写)
        .def_readwrite("name", &Pet::name)

        // 通过getter/setter绑定私有属性
        .def_property("age", &Pet::get_age, &Pet::set_age)

        // 绑定成员函数
        .def("set_age", &Pet::set_age, py::arg("age"))

        // 绑定静态函数
        .def_static("species", &Pet::species);
}

Python 端使用:

cpp 复制代码
p = example.Pet("旺财")
print(p.name)       # 旺财
p.age = 3
print(p.age)        # 3
print(example.Pet.species())  # dog

3.2.2.类继承

绑定派生类时,在 py::class_ 第二个模板参数指定父类,自动继承父类已绑定的方法。

cpp 复制代码
class Dog : public Pet {
public:
    Dog(const std::string& name) : Pet(name) {}
    std::string bark() const { return "汪汪汪"; }
};

PYBIND11_MODULE(example, m) {
    // 先绑定父类
    py::class_<Pet>(m, "Pet")
        .def(py::init<const std::string&>())
        .def_readwrite("name", &Pet::name);

    // 绑定子类,第二个模板参数声明继承关系
    py::class_<Dog, Pet>(m, "Dog")
        .def(py::init<const std::string&>())
        .def("bark", &Dog::bark);
}

3.2.3.虚函数与多态(Trampoline 类)

如果需要在 Python 中继承 C++ 类并重写虚函数,必须定义中间 "跳板类(Trampoline)"。

cpp 复制代码
// C++ 基类
class Base {
public:
    virtual ~Base() = default;
    virtual std::string say() const { return "I am Base"; }
};

// Trampoline 跳板类
class PyBase : public Base {
public:
    using Base::Base; // 继承构造函数

    std::string say() const override {
        PYBIND11_OVERRIDE(
            std::string,  // 返回值类型
            Base,         // 父类类型
            say           // 被重写的函数名
        );
    }
};

PYBIND11_MODULE(example, m) {
    // 绑定基类,同时注册跳板类
    py::class_<Base, PyBase>(m, "Base")
        .def(py::init<>())
        .def("say", &Base::say);
}

Python 端继承并重写:

cpp 复制代码
class MyDerived(example.Base):
    def say(self):
        return "I am Python Derived"

obj = MyDerived()
print(obj.say())  # 输出 I am Python Derived

纯虚函数使用 PYBIND11_OVERRIDE_PURE 宏,用法完全一致。

3.3.数据类型自动转换

pybind11 内置了常用类型的转换,扩展类型需要引入对应头文件。

3.3.1.内置自动转换类型

无需额外头文件即可自动转换:

  • 基础类型:int、float、bool、char*
  • 字符串:std::string ↔ str
  • 指针与引用:裸指针、const 引用

3.3.2.STL 容器转换

引入 <pybind11/stl.h> 后,标准容器可自动双向转换:

  • std::vector / std::list / std::array ↔ list
  • std::map / std::unordered_map ↔ dict
  • std::set / std::unordered_set ↔ set
  • std::tuple ↔ tuple
cpp 复制代码
#include <pybind11/stl.h>
#include <vector>
#include <map>

std::vector<int> get_list() {
    return {1, 2, 3, 4, 5};
}

std::map<std::string, int> get_dict() {
    return {{"a", 1}, {"b", 2}};
}

PYBIND11_MODULE(example, m) {
    m.def("get_list", &get_list);
    m.def("get_dict", &get_dict);
}

注意:STL 转换是值拷贝,大数据量会有性能开销;高频批量数据推荐用 NumPy 零拷贝方案。

3.3.3.枚举类型

使用 py::enum_ 绑定 C++ 枚举,支持强类型枚举 enum class。

cpp 复制代码
enum class Color { Red, Green, Blue };

PYBIND11_MODULE(example, m) {
    py::enum_<Color>(m, "Color")
        .value("Red", Color::Red)
        .value("Green", Color::Green)
        .value("Blue", Color::Blue)
        .export_values(); // 导出到模块命名空间
}

3.4.NumPy 数组交互(核心高频场景)

引入 <pybind11/numpy.h> 后,可以直接操作 NumPy 数组,支持零拷贝访问内存,是科学计算、AI 推理项目的核心功能。

示例:

cpp 复制代码
#include <pybind11/numpy.h>
namespace py = pybind11;

// 两个一维double数组逐元素相加
py::array_t<double> array_add(py::array_t<double> a, py::array_t<double> b) {
    // 获取数组buffer信息(指针、shape、stride等)
    auto buf_a = a.request();
    auto buf_b = b.request();

    // 校验维度和长度
    if (buf_a.ndim != 1 || buf_b.ndim != 1)
        throw std::runtime_error("仅支持一维数组");
    if (buf_a.shape[0] != buf_b.shape[0])
        throw std::runtime_error("数组长度必须一致");

    size_t n = buf_a.shape[0];
    // 创建输出数组
    auto result = py::array_t<double>(n);
    auto buf_res = result.request();

    // 获取裸指针,直接操作内存(零拷贝)
    double* ptr_a = static_cast<double*>(buf_a.ptr);
    double* ptr_b = static_cast<double*>(buf_b.ptr);
    double* ptr_res = static_cast<double*>(buf_res.ptr);

    for (size_t i = 0; i < n; ++i) {
        ptr_res[i] = ptr_a[i] + ptr_b[i];
    }

    return result;
}

PYBIND11_MODULE(example, m) {
    m.def("array_add", &array_add, py::arg("a"), py::arg("b"));
}

Python 端调用:

cpp 复制代码
import numpy as np
import example

a = np.array([1.0, 2.0, 3.0], dtype=np.float64)
b = np.array([4.0, 5.0, 6.0], dtype=np.float64)
c = example.array_add(a, b)
print(c)  # [5. 7. 9.]

关键说明

  • request() 返回数组的完整内存布局信息,包括 ptr(数据指针)、shape(维度)、strides(步长)、ndim(维度数)。
  • 零拷贝前提:数组是连续内存(C 风格或 Fortran 风格);非连续数组会自动触发拷贝。
  • 支持任意维度数组,通过 shape[i] 访问各维度长度。

3.5.高级特性

3.5.1.GIL 全局解释器锁管理

C++ 扩展默认持有 Python GIL,耗时计算会阻塞整个 Python 进程。纯 C++ 计算逻辑可以手动释放 GIL,提升并发性能。

cpp 复制代码
void heavy_computation() {
    // 释放GIL,此区域内绝对不能操作任何Python对象
    py::gil_scoped_release release;

    // 纯C++耗时计算
    double sum = 0;
    for (int i = 0; i < 1e9; ++i) {
        sum += i * 0.001;
    }

    // 出作用域自动重新获取GIL
}

如果在已释放 GIL 的区域需要临时访问 Python 对象,用 py::gil_scoped_acquire 重新获取:

cpp 复制代码
py::gil_scoped_acquire acquire;
// 此处可以安全操作Python对象

3.5.2.智能指针

pybind11 原生支持 std::shared_ptr,会自动与 Python 引用计数联动,避免内存泄漏。

cpp 复制代码
#include <memory>

class MyClass {
public:
    MyClass(int id) : m_id(id) {}
    int get_id() const { return m_id; }
private:
    int m_id;
};

std::shared_ptr<MyClass> create_obj(int id) {
    return std::make_shared<MyClass>(id);
}

PYBIND11_MODULE(example, m) {
    py::class_<MyClass, std::shared_ptr<MyClass>>(m, "MyClass")
        .def("get_id", &MyClass::get_id);

    m.def("create_obj", &create_obj, py::arg("id"));
}

注意:std::unique_ptr 仅支持转移语义,返回后 Python 独占对象所有权。

3.5.3.回调函数(Python 函数传入 C++)

引入 <pybind11/functional.h> 后,可将 Python 函数作为 std::function 传入 C++ 执行。

cpp 复制代码
#include <pybind11/functional.h>
#include <functional>

void foreach(int n, const std::function<void(int)>& callback) {
    for (int i = 0; i < n; ++i) {
        callback(i);
    }
}

PYBIND11_MODULE(example, m) {
    m.def("foreach", &foreach, py::arg("n"), py::arg("callback"));
}

Python 端使用:

cpp 复制代码
def print_num(x):
    print(f"收到: {x}")

example.foreach(5, print_num)

3.5.4.异常处理

  • C++ 抛出的 std::exception 会自动转换为 Python 的 RuntimeError。
  • 可以注册自定义异常类型,在 Python 端精准捕获。
cpp 复制代码
// 自定义异常
class MyError : public std::exception {
public:
    const char* what() const noexcept override {
        return "自定义业务错误";
    }
};

PYBIND11_MODULE(example, m) {
    // 注册自定义异常
    py::register_exception<MyError>(m, "MyError");

    m.def("test_error", []() {
        throw MyError();
    });
}

Python 端捕获:

cpp 复制代码
try:
    example.test_error()
except example.MyError as e:
    print(e)

4.常见问题

1.生命周期安全

不要返回局部变量的裸指针或引用,Python 端会访问野指针。优先使用 std::shared_ptr 管理对象生命周期。

2.性能优化

  • 避免在 C++ 循环中频繁调用 Python 对象 / 回调,尽量批量处理。
  • 大数据交互优先用 NumPy 零拷贝,避免 STL 容器的值拷贝。
  • 纯计算逻辑务必释放 GIL。

3.字符串编码

std::string 与 Python str 默认按 UTF-8 转换。Windows 下涉及宽字符时,使用 py::str 的 encode/decode 显式处理。

4.调试技巧

  • Linux 下可用 gdb 附加 Python 进程调试 C++ 代码。
  • Windows 下可用 Visual Studio 附加 python.exe 进行源码级调试。
  • 编译时开启 Debug 模式,保留符号信息。

5. 和其他方案对比

  • pybind11:C++11,header-only,现代简洁,ML 领域最流行
  • Boost.Python:老方案,依赖庞大 Boost 库,现在很少新项目用
  • Cython:类 Python 语法,不是纯 C++,需要学新语法
  • ctypes:C 接口,不支持 C++ 类,需要写 C wrapper
相关推荐
CodeYp20191 小时前
第11讲:stack 和 queue
开发语言·c++·queue·stack
炘爚1 小时前
数据结构算法总结
数据结构·python·算法
信誓旦旦的程序猿1 小时前
【Python量化实战宝典 #18】MACD 金叉死叉手册:DIF、DEA、柱体全拆解(Python 可跑)
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
Patrick在香港1 小时前
一个加 1 的动作,把最大值变成了最小值
python·numpy·数据类型·整数溢出·数据精度
咩咩啃树皮1 小时前
JavaScript 异步流程为什么容易失控?从回调到可维护设计
开发语言·javascript·ecmascript
沫璃染墨2 小时前
《Qt从零入门系列(十五):Qt线程安全与锁机制详解——从QMutex到RAII与读写锁实战》
开发语言·c++·qt·安全·交互·线程·qt6.3
hetao17338372 小时前
2026-10-25 hetao1733837 的刷题记录
c++·算法
Misnearch2 小时前
第7章 面向对象技术
java·开发语言·软考
weixin_BYSJ19872 小时前
电竞先锋网站---附源码02032
java·vue.js·spring boot·python·django·tomcat·php