Skip to content

Content

This module relies on:

  • https://github.com/leskovec/pyC_intro

The central idea is that a pybind11 project contains a small binding file that tells Python what C++ functions (and later, classes) it can call.

A minimal pybind11 binding file

Here is a very simple pybind11 binding file. Compiling it produces a shared library that Python can import as a module (here: hello).

#include <pybind11/pybind11.h>

#include "hello.h"

PYBIND11_MODULE(hello, m) {
    m.doc() = "pybind11 hello plugin";
    m.def("say_hello", &say_hello, "A function which says hello");
}

Let’s unpack this line by line, because this is the whole “bridge” between C++ and Python.

1) The pybind11 header

#include <pybind11/pybind11.h>

This is the core pybind11 interface. It provides:

  • the PYBIND11_MODULE(...) macro (the module’s import entry point)
  • the C++ types used to represent Python objects (like the module object m)
  • the registration helpers like m.def(...)

Conceptually: this header gives you the tools to construct a Python module from C++.

2) Your C++ code stays normal C++

#include "hello.h"

This is an underappreciated point: the function you expose (here say_hello) lives in your own header and can remain ordinary C++ code. In real research code, the computational routines usually already exist. pybind11 is just the thin layer that exposes selected routines to Python.

3) The module entry point

PYBIND11_MODULE(hello, m) { ... }

This macro defines the initialization function that Python calls when it runs:

import hello
  • hello is the Python module name
  • m is the module object you populate with functions, classes, constants, etc.

Read it as: “When Python imports hello, create the module m and register things into it.”

4) m.doc() sets the module docstring

m.doc() = "pybind11 hello plugin";

Python modules have docstrings, just like functions. Setting m.doc() makes interactive use nicer:

import hello
print(hello.__doc__)
help(hello)

In a workshop, this is also a sanity check that you imported the module you think you imported.

5) m.def(...) exposes a C++ function to Python

m.def("say_hello", &say_hello, "A function which says hello");

This line registers a Python-visible function:

  • "say_hello" is the name Python users will call: hello.say_hello()
  • &say_hello is a pointer to the C++ function
  • the last string is the function docstring shown by help(hello.say_hello)

When Python calls hello.say_hello(), pybind11 converts Python arguments (if any) into C++ values, calls the C++ function, and converts the return value back into a Python object.

What this example is really teaching

Even though this is tiny, it contains the full pattern you’ll reuse throughout the workshop:

  1. Your “real” C++ code lives in your own headers/sources.
  2. The binding file includes those headers.
  3. Everything Python can see is registered inside PYBIND11_MODULE.

Once this is clear, the rest becomes less mystical: you’re writing “registration code” and choosing a good Python-facing API.