pybind11

The pybind11 library allows you to call C++ code from python. It is a header-only library, and requires that you only add a few lines describing your module and C++ functions to the source to get it to work.

You can install pybind11 via pip:

pip install pybind11

Since it is a header-only library, the main thing you need in order to compile source is the location of the headers (so you can specify the include path via the -I option). You can find this via:

python -m pybind11 --includes

on my machine, this gives:

-I/usr/include/python3.12 -I/home/zingale/.local/lib/python3.12/site-packages/pybind11/include

When we compile the .cpp code, we need to make a shared-object library that can be imported into python as a module. This needs to have a specific name, with the suffix given by

python3-config --extension-suffix

on my machine, this gives:

.cpython-312-x86_64-linux-gnu.so

Simple function example

Here’s a very simple example. We will write a C++ function that computes \(f(x) = \sin(x)\).

Listing 146 simple.cpp
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

#include <cmath>

double f(double x) {
    return std::sin(x);
}

PYBIND11_MODULE(simple, m) {
    m.doc() = "simple example of creating a function f(x) in C++";
    m.def("f", &f);
}

We add a PYBIND11_MODULE macro that defines the entry point from python—see: https://pybind11.readthedocs.io/en/stable/reference.html#macros

To compile it, we can use this GNUmakefile:

Listing 147 GNUmakefile
SOURCES := $(wildcard *.cpp)
OBJECTS := $(SOURCES:.cpp=.o)

BASE := simple

HEADERS := $(wildcard *.H)

PYBIND_INCLUDES := $(shell python -m pybind11 --includes)

PYBIND_SUFFIX := $(shell python3-config --extension-suffix)

PYTHON_LIBRARY := ${BASE}${PYBIND_SUFFIX}

ALL: ${PYTHON_LIBRARY}

CXXFLAGS := -O3  -Wall -Wextra -shared -std=c++17 -fPIC ${PYBIND_INCLUDES}

%.o : %.cpp
	g++ ${CXXFLAGS} -c $<

${PYTHON_LIBRARY}: ${OBJECTS} ${HEADERS}
	g++ ${CXXFLAGS} -o $@ $<


print-%: ; @echo $* is $($*)

We can use this in python as:

import simple
simple.f(1.0)