pybind11
Header-only C++11 library for building Python extension modules and embedding Python in C++ applications. A small set of templates maps functions, classes, inheritance, exceptions, standard-library containers, NumPy arrays, buffers, callbacks, and ownership policies between the two languages. The PyPI package supplies headers and CMake discovery files; your compiler still produces a platform- and Python-specific binary extension. It is a binding layer, not a compiler toolchain, wheel builder, or automatic wrapper generator.
The standard practical choice for hand-written C++ to Python bindings, with excellent coverage and documentation. The library is the easy part; only install it if you are ready to own native builds, wheels, ABI coverage, GIL rules, and object lifetimes.
Use it if
- You have an existing modern C++ library and want a deliberate Python API with direct access to its types and functions
- Performance-critical code can operate on NumPy buffers without copying and release the GIL during long native work
- Your team understands C++ object lifetimes, templates, compilers, and Python packaging well enough to own binary wheels
- You need two-way interop, including Python subclasses, callbacks, or embedding CPython inside a C++ process
- You only need to speed up Python loops and have no C++ codebase: Cython, Numba, vectorized NumPy, or a native library binding usually creates less build and ownership work
- You cannot publish wheels for every supported Python, operating system, CPU, and C library combination: pip may fall back to a local C++ build when no compatible wheel exists
- Your project must remain on Python 3.8 or MSVC 2017: pybind11 3.1.0 explicitly removed both from its supported set
- You need a stable binary ABI across Python releases: ordinary extension modules are tied to interpreter and platform ABIs unless you deliberately target limited-API support and test its restrictions
- Your boundary has complicated shared ownership or callbacks but nobody can audit lifetimes: incorrect return-value policies, dangling references, double deletion, and GIL mistakes can crash the process rather than raise a Python exception
Setup reality
pip install pybind11 installs headers, a pybind11-config helper, and CMake package metadata; it does not install a C++ compiler, CMake, Ninja, Python development headers, or a wheel-building pipeline. Version 3.1.0 requires Python 3.9+, a C++11-capable toolchain, and has dropped MSVC 2017. Local development can use CMake with find_package(pybind11 CONFIG REQUIRED), but distributable packages need a build backend such as scikit-build-core or setuptools configured in pyproject.toml. Build isolation means pybind11 must appear in build-system.requires even if it is also installed in your virtual environment. Every target platform needs matching compilers and system SDKs, and Linux wheels normally need manylinux-compatible builds plus auditwheel repair; macOS universal2 and Windows wheels are separate artifacts. A source distribution asks end users to reproduce this setup, so serious projects publish wheels through CI. Binding code compiles templates in every translation unit and error messages can be long; keep bindings split but avoid inconsistent module definitions. NumPy conversion is optional header support, yet zero-copy arrays require exact dtype, shape, stride, alignment, and lifetime decisions. Automatic STL conversion often copies containers. Python exceptions must not cross unmanaged threads, Python callbacks require the GIL, and long C++ work should release it only when no Python objects are touched. Ownership is the hardest part: choose return_value_policy and keep_alive from the real C++ lifetime, not from what makes one test pass. Test release builds, interpreter shutdown, subclassing, and wheel installation in clean environments.
Patterns
Expose a C++ functionbind-function
#include <pybind11/pybind11.h>
namespace py = pybind11;
int add(int a, int b) { return a + b; }
PYBIND11_MODULE(example, m) {
m.doc() = "Example native module";
m.def("add", &add, py::arg("a"), py::arg("b"), "Add two integers");
}The name in PYBIND11_MODULE must match the compiled extension module name or import fails.
Build a module with CMakeconfigure-cmake
cmake_minimum_required(VERSION 3.15)
project(example LANGUAGES CXX)
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(example src/bindings.cpp)
target_compile_features(example PRIVATE cxx_std_17)Install pybind11 into the build environment or pass its CMake directory; a runtime dependency alone is not enough.
Declare an isolated wheel buildpackage-with-scikit-build
[build-system]
requires = ["scikit-build-core>=0.10", "pybind11>=3.1"]
build-backend = "scikit_build_core.build"
[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.9"
[tool.scikit-build]
cmake.build-type = "Release"Build-system requirements are installed in an isolated environment; do not rely on packages from the active virtualenv.
Expose a class and propertiesbind-class
py::class_<Point>(m, "Point")
.def(py::init<double, double>(), py::arg("x"), py::arg("y"))
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y)
.def_property_readonly("length", &Point::length)
.def("__repr__", [](const Point &p) {
return "Point(" + std::to_string(p.x) + ", " + std::to_string(p.y) + ")";
});The C++ type must remain valid for the Python wrapper's lifetime; choose a holder type when ownership is shared.
Use shared ownershipbind-smart-pointer
py::class_<Session, std::shared_ptr<Session>>(m, "Session")
.def(py::init<std::string>())
.def("close", &Session::close);
m.def("current_session", ¤t_session);Holder types must be consistent across a class hierarchy. Do not introduce shared_ptr solely to hide an unclear native lifetime.
Convert vectors and dictionariesconvert-stl-containers
#include <pybind11/stl.h>
std::vector<std::string> names();
std::map<std::string, int> counts();
PYBIND11_MODULE(example, m) {
m.def("names", &names);
m.def("counts", &counts);
}Automatic STL conversion usually copies between C++ and Python containers; it is convenient but not zero-copy.
Process a contiguous NumPy arrayaccept-numpy-array
#include <pybind11/numpy.h>
double sum(py::array_t<double, py::array::c_style | py::array::forcecast> values) {
auto v = values.unchecked<1>();
double total = 0;
for (py::ssize_t i = 0; i < v.shape(0); ++i) total += v(i);
return total;
}forcecast may allocate a converted copy. Remove it when rejecting the wrong dtype or layout is preferable.
Release the GIL for native workrelease-gil
m.def(
"simulate",
&simulate,
py::arg("steps"),
py::call_guard<py::gil_scoped_release>()
);The called C++ function must not touch Python objects or call Python APIs while the GIL is released.
Invoke a Python callback safelycall-python-callback
void run_with_callback(const std::function<void(int)> &callback) {
for (int i = 0; i < 10; ++i) callback(i);
}
PYBIND11_MODULE(example, m) {
m.def("run_with_callback", &run_with_callback);
}Include pybind11/functional.h for std::function conversion. Calls from native threads must acquire the GIL.
Map a native exceptiontranslate-exception
class ParseError : public std::runtime_error {
using std::runtime_error::runtime_error;
};
PYBIND11_MODULE(example, m) {
py::register_exception<ParseError>(m, "ParseError");
m.def("parse", &parse);
}Known standard exceptions already map to Python types; register domain exceptions when callers need to catch them specifically.
Declare a borrowed child lifetimereturn-borrowed-reference
py::class_<Store>(m, "Store")
.def(
"item",
&Store::item,
py::return_value_policy::reference_internal
);reference_internal keeps the parent wrapper alive with the child. It does not repair a C++ object that can be invalidated internally.
Add a trampoline for Python subclassessupport-python-overrides
class PyAnimal : public Animal {
public:
using Animal::Animal;
std::string sound() const override {
PYBIND11_OVERRIDE_PURE(std::string, Animal, sound);
}
};
py::class_<Animal, PyAnimal>(m, "Animal")
.def(py::init<>())
.def("sound", &Animal::sound);Every virtual method meant for Python override needs trampoline coverage; pure overrides raise if Python provides none.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| nanobind | PyPI | You want a newer pybind11-like binding system focused on smaller binaries and faster compile or call performance |
| cython | PyPI | You are accelerating Python-oriented code or wrapping C APIs and prefer a Python-like compiled language |
| cffi | PyPI | You bind a C ABI and want to avoid C++ template bindings and most CPython extension details |