oop::notes

用 pybind11 把 C++ 函数暴露给 Python

2026-08-14 · #cpp#python

工作里有一段数值计算用 Python 写太慢,改成 C++ 后需要给同事在 Python 里调用。试过 ctypes,要手写一堆类型声明,最后还是用了 pybind11。

最小例子

// fastmath.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

int add(int a, int b) { return a + b; }

PYBIND11_MODULE(fastmath, m) {
    m.doc() = "a few fast helpers";
    m.def("add", &add, py::arg("a"), py::arg("b"));
}

编译:

pip install pybind11
c++ -O2 -shared -std=c++17 -fPIC \
    $(python3 -m pybind11 --includes) fastmath.cpp \
    -o fastmath$(python3-config --extension-suffix)
>>> import fastmath
>>> fastmath.add(a=2, b=3)
5

传 numpy 数组

用 py::array_t 可以直接拿到底层缓冲区,不需要拷贝:

#include <pybind11/numpy.h>

double sum(py::array_t<double, py::array::c_style | py::array::forcecast> arr) {
    auto buf = arr.unchecked<1>();
    double s = 0;
    for (py::ssize_t i = 0; i < buf.shape(0); ++i) s += buf(i);
    return s;
}

forcecast 让传进来的 int 数组也能自动转成 double,代价是会发生一次拷贝。如果确定输入类型,可以去掉它,类型不对时直接报错。

什么时候释放 GIL

纯 C++ 计算、不碰任何 Python 对象的部分,可以释放 GIL,让其他 Python 线程同时跑:

double heavy(py::array_t<double> arr) {
    auto buf = arr.unchecked<1>();   // 在持有 GIL 时取到视图
    py::gil_scoped_release release;  // 下面的作用域里不持有 GIL
    double s = 0;
    for (py::ssize_t i = 0; i < buf.shape(0); ++i) s += buf(i) * buf(i);
    return s;
}
释放 GIL 之后,绝对不能创建、销毁或修改任何 Python 对象,包括返回 py::object。需要的话用 py::gil_scoped_acquire 重新拿回来。

用 CMake 构建

项目大了以后还是交给 CMake:

cmake_minimum_required(VERSION 3.18)
project(fastmath LANGUAGES CXX)
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(fastmath fastmath.cpp)