优先选nanobind写新项目,维护旧代码则继续用pybind11;nanobind编译快、二进制小、API简洁、调用开销低但错误提示简陋,pybind11文档丰富、容错友好但语法冗长。

nanobind 和 pybind11 到底该选哪个
如果你只打算写新项目,优先选 nanobind;如果要维护已有 pybind11 代码,别急着迁,它依然稳定可用。nanobind 编译更快、二进制更小、API 更紧凑,但 pybind11 的文档和社区案例更多——尤其遇到冷门 STL 类型或自定义异常时,pybind11 的容错提示往往更友好。
关键区别在底层:nanobind 直接对接 CPython C API,跳过了 pybind11 的抽象层,所以绑定函数调用开销略低,但调试时堆栈更“裸”,错误信息也更简短(比如只报 TypeError: expected int, got float,不告诉你具体哪行绑定出的问题)。
绑定一个带重载的 C++ 成员函数
pybind11 需要显式用 py::overload_cast 区分重载,nanobind 则靠参数类型自动推导,写起来少一半代码,但容易因隐式转换失败而静默退化到调用错误重载。
- pybind11 写法:
m.def("process", static_cast<int>(&MyClass::process));</int> - nanobind 写法:
nb::def("process", &MyClass::process, nb::arg("x")); // 自动匹配 int 版本 - 坑点:如果
MyClass::process还有个process(double),且 Python 传入的是3.0,nanobind 可能意外调用int版本(先转成 int),而 pybind11 会明确报错要求指定重载
返回 std::vector 或自定义容器时的内存管理
默认情况下,两者都把 std::vector 拷贝成 Python list,性能差、不共享内存。想零拷贝,必须显式声明所有权转移或引用语义。
立即学习“Python免费学习笔记(深入)”;
- pybind11:用
py::return_value_policy::move或py::return_value_policy::reference_internal,但后者要求 C++ 对象生命周期长于 Python 引用 - nanobind:默认策略是
nb::rv_policy::automatic,对局部std::vector自动 move,对成员变量默认 copy;想强制 move,得写nb::rv_policy::move - 常见错误:返回局部
std::vector却没设 move 策略 → Python 得到空 list 或崩溃 - 安全做法:对大数组,统一用
nb::ndarray(nanobind)或py::array(pybind11)替代std::vector,直接对接 NumPy
Windows 下编译失败的典型原因
nanobind 在 MSVC 上对模板实例化更敏感,常卡在 nb::class_<T> 声明时找不到符号;pybind11 则更容易在链接阶段报 LNK2019: unresolved external symbol,尤其混用 /MD 和 /MT。
- nanobind 必须确保所有模板类定义在头文件中(不能分离 .h/.cpp),否则 Windows 下无法导出
- pybind11 要检查 CMake 中是否设置了
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON),否则 DLL 导出符号可能遗漏 - 两者都要求 Python 解释器版本、架构(x64/ARM64)、运行时(/MD)完全匹配,
python -c "import sys; print(sys.version, sys.maxsize)"和 CMake 输出的Python_EXECUTABLE必须一致
最麻烦的不是语法,是 ABI 兼容性细节——比如 nanobind 0.5+ 默认禁用 RTTI,若你的 C++ 库依赖 dynamic_cast,就得手动开 -D NANOBIND_ENABLE_RTTI=ON,而且必须和 Python 解释器编译选项对齐。











