18. pybind11: Use C++ libraries#
pybind11 lets you write Python extensions using pure C++; no special tool or processing step needed. It’s just a header-only library that works just about everywhere. Used by SciPy, PyTorch, and many more libraries.
18.1. Example#
A Python extension in pybind11 looks like this:
#include <pybind11/pybind11.h>
namespace py = pybind11;
int square(int x) {
return x * x;
}
PYBIND11_MODULE(somecode, m) {
m.def("square", &square);
}
You can use cppimport
to import it for a quick test, or a build system like scikit-build-core to build. I’m not including a compiler in this environment, so I’m not going to build here - see one of my other classes. This is a minimal CMakeLists.txt
:
cmake_minimium_required(VERSION 3.15...3.26)
project(python_example LANGUAGES CXX)
set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(python_example MODULE src/main.cpp)
install(TARGETS python_example DESTINATION .)
And, your pyproject.toml:
[build-system]
requires = ["scikit-build-core", "pybind11"]
build-backend = "scikit_build_core.build"
[project]
name = "example"
version = "0.0.1"
requires-python = ">=3.8"
If you want to build and distribute, use cibuildwheel, which is used by Scikit-Learn, Matplotlib, MyPy, and many more; it can be setup for Linux, macOS, and Windows and all common CPython and PyPy versions in just 13 lines:
on: [push, pull_request]
jobs:
build_wheels:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: pypa/cibuildwheel@v2.14
- uses: actions/upload-artifact@v3
with:
path: ./wheelhouse/*.whl
18.2. So much more#
Some examples of classes:
#include <pybind11/operators.h>
using namespace pybind11::literals;
PYBIND11_MODULE(example, m) {
py::class_<Vector>(m, "Vector")
.def(py::init<double, double>())
.def_property("x", &Vector::getX, &Vector::setX)
.def_property("y", &Vector::getY, &Vector::setY)
.def("mag", &Vector::mag, "I am a mag function")
.def("unit", [](const Vector& self){return self.unit();})
.def("__str__", [](const Vector& self){return py::str("[{}, {}]").format(self.getX(), self.getY());})
.def(py::self *= float())
.def(float() * py::self)
.def(py::self * float())
}
You can use lambda functions almost anywhere, and you can ask for py::object
or the C++ type interchangeably, or cast between them.