mrkeyoor.com_
Sat 08 Aug 17:39 UTC
PyPICLI & Toolingupdated 08 Aug 2026

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.

Verdict

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.

API stability4/5The PYBIND11_MODULE, module.def, class_, argument, conversion, exception, buffer, and call-guard patterns are mature and widely used. Major version 3 still changes supported toolchains and advanced behavior: 3.1 removed Python 3.8 and MSVC 2017 and changed strict numeric conversions. Core bindings are stable, but review changelogs before compiler or Python upgrades.
Docs5/5The Read the Docs manual is extensive, versioned, and covers first modules, CMake, classes, conversions, NumPy, embedding, GIL, exceptions, smart pointers, callbacks, advanced ownership, and upgrade notes. The README links maintained examples for setuptools, scikit-build, and CMake. Some advanced lifetime guidance is necessarily dense, but the evidence is available.
Maintenance5/5Version 3.1.0 was released on August 6, 2026, and the repository was pushed on August 8, 2026. The release tracks current interpreters and compilers, adds subinterpreter facilities, and removes obsolete support with explicit notes. The project tests CPython, PyPy, and GraalPy and remains active across a large contributor and downstream base.
Ecosystem5/5It sees roughly 6,923,673 weekly downloads and the repository has 17,986 stars. pybind11 is supported by CMake, setuptools, scikit-build-core, cibuildwheel, conda-forge, NumPy, and common wheel-repair tooling. Many scientific and systems projects use its conventions, making binding examples and packaging knowledge easier to find.

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
Skip it if

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", &current_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

PackageRegistryPick it when
nanobindPyPIYou want a newer pybind11-like binding system focused on smaller binaries and faster compile or call performance
cythonPyPIYou are accelerating Python-oriented code or wrapping C APIs and prefer a Python-like compiled language
cffiPyPIYou bind a C ABI and want to avoid C++ template bindings and most CPython extension details