用 pybind11 把 C++ 函数暴露给 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)