Slow Python? Meet pybind11 !
It started with a simple curiosity…
While exploring automatic differentiation (autograd) in modern Deep Learning frameworks, we wanted to understand how gradients are computed behind the scenes.
Tell me, I may forget..
Show me, I may remember..
Let me implement, I truly learn !
Per the saying, We tried implementing Andrej Karpathy’s micrograd, a tiny autograd engine written in pure Python built for educational purpose which explains minimal functions of autograd in about 50–100 lines of code.
After implementation, arose a natural question..
If Python is slow, how are Deep Learning frameworks so fast ??
This led us down another rabbit hole. Digging further had us discovering that many high-performance Python libraries rely heavily on C++ under the hood, with Python acting as a clean and flexible interface layer. This led us to explore how Python and C++ can work together and that’s when we encountered pybind11 !
What is pybind11 ?
pybind11 is a lightweight, header-only C++ library that enables seamless interoperability between C++ and Python. It allows developers to expose C++ classes, functions, and data structures directly to Python — without writing complex boilerplate code
In Nutshell: pybind11 lets you write performance-critical code in C++ and use it like normal Python code.
Why pybind11 ?
Before pybind11,
- Integrating Python with C++ meant dealing with the low-level Python/C API, manual memory management, and large amounts of boilerplate code.
After pybind11,
- Modern C++11 features like templates, type inference, and smart pointers to automatically handle bindings and memory safely.
The result is clean, minimal, and Pythonic code that makes connecting C++ performance with Python usability significantly easier.
A Tiny example
With pybind11, exposing a simple C++ function to Python can be as small as:
int add(int i, int j) {
return i + j;
}Binding it with pybind11:
PYBIND11_MODULE(example, m) {
m.def("add", &add, "A function that adds two numbers");
}Now in Python:
import example
example.add(2, 3)Where is pybind11 Used?
pybind11 is widely used in performance-critical Python libraries and research projects. Some notable users include
- PyTorch
- TensorFlow
- ONNX Runtime
- Open3D
This reinforces an important idea:
Many “Python” libraries are actually C++ engines with Python interfaces.
Core Concepts of pybind11
1.Module Binding — Create a Python module from C++ code
PYBIND11_MODULE(module_name, m)2. Function Binding — Expose C++ functions
m.def("function_name", &function);3. Class Binding — Expose C++ classes
py::class_<ClassName>(m, "ClassName")4. Type Conversion — Automatic conversion between:
- Python lists ↔ C++ vectors
- Python ints ↔ C++ ints
- NumPy arrays ↔ C++ buffers
This is one of pybind11’s most powerful features
Get Bug Theory’s stories in your inbox
Join Medium for free to get updates from this writer.
With these simple concepts, we can bridge Python and C++ seamlessly.
And that’s exactly what we did next !
We implemented micrograd in C++ and exposed it to Python using pybind11 !!
Let’s dive in..
Implementing Micrograd in C++ with pybind11
To explore Python and C++ interoperability, we re-implemented micrograd in C++ and exposed it to Python using pybind11. Micrograd itself is a tiny autograd engine (~100 lines) designed for educational purposes, making it ideal for experimentation.
Our implementation:
Github: https://github.com/kavinkumar807/micrograd_pybind_cpp
Project Structure:
value.h # Core C++ Value struct with forward ops and backward pass
bindings.cpp # pybind11 bindings exposing Value to Python
setup.py # Build script (distutils/setuptools extension)
requirements.txt # Python dependencies (pybind11, setuptools)
backprop_test.py # Example usage / sanity checkStep 1: C++ Implementation (value.h)
We implemented the core Value class in C++ similar to Python micrograd
struct Value : public std::enable_shared_from_this<Value> {
double data;
double grad;
std::vector<std::shared_ptr<Value>> prev;
std::function<void()> _backward;
Value(double data) : data(data), grad(0.0) {}
// addition
std::shared_ptr<Value> add(std::shared_ptr<Value> other) {
auto out = std::make_shared<Value>(this->data + other->data);
out->prev = {shared_from_this(), other};
out->_backward = [self=shared_from_this(), other, out]() {
self->grad += 1.0 * out->grad;
other->grad += 1.0 * out->grad;
};
return out;
}
// multiplication
std::shared_ptr<Value> mul(std::shared_ptr<Value> other) {
auto out = std::make_shared<Value>(this->data * other->data);
out->prev = {shared_from_this(), other};
out->_backward = [self=shared_from_this(), other, out]() {
self->grad += other->data * out->grad;
other->grad += self->data * out->grad;
};
return out;
}
}The below (bindings.cpp) exposes the Value class directly to Python.
PYBIND11_MODULE(micrograd_cpp, m) {
py::class_<Value, std::shared_ptr<Value>>(m, "Value")
.def(py::init<double>())
.def_readwrite("data", &Value::data)
.def_readwrite("grad", &Value::grad)
.def("backward", &Value::backward)
// methods
.def("add", &Value::add)
.def("mul", &Value::mul)
// python operators
.def("__add__", &Value::add)
.def("__mul__", &Value::mul);
}Step 2: setup.py
We use setup.py to compile the C++ code into a Python extension module (.so file)
from setuptools import setup, Extension
import pybind11
ext_modules = [
Extension(
"micrograd_cpp",
["bindings.cpp"],
include_dirs=[pybind11.get_include()],
language="c++",
),
]
setup(
name="micrograd_cpp",
version="0.1",
ext_modules=ext_modules,
)pybind11 integrates directly with setuptools, making compilation straightforward.
Step 3: Install Dependencies
pip install pybind11Step 4: Build .so File
Generate the shared object file
python setup.py build_ext --inplaceThis generates,
micrograd_cpp.cpython-*.soThis .so file is the compiled C++ module that Python can import.
Step 5: Using in Python
Now import like a normal Python module
import micrograd_cpp as mg
a = mg.Value(2.0)
b = mg.Value(3.0)
c = a * b + a # c = a*b + a => c = 8.0
c.backward()
print(a.grad) # 4.0 (dc/da = b + 1 = 3 + 1)
print(b.grad) # 2.0 (dc/db = a = 2)That’s it !! Python calling C++ seamlessly !
What Just Happened?
- C++ code compiled to
.so - pybind11 exposed classes/functions
- Python imported compiled module
- C++ executed under the hood
Python simplicity; C++ performance;
Final Thoughts
What started as curiosity about autograd led us to explore something deeper: how Python leverages C++ for performance.
By re-implementing micrograd in C++ and exposing it with pybind11, we recreated a simplified version of the architecture used in many high-performance Python libraries.
PS: It’s also worth noting that pybind11 is not the only approach for language bindings. There are several alternatives such as SWIG, Boost.Python, Cython, and the native Python/C API — each with its own trade-offs and use cases.
< Not AI Generated :P >
Authors: Kavinkumar Baskar, Kavyapriya Govindarajan
References
pybind11
https://medium.com/@manish.surya.r/a-minimal-pybind11-example-calling-c-from-python-61352b8306da
https://medium.com/@ahmedfgad/pybind11-tutorial-binding-c-code-to-python-337da23685dc
https://medium.com/@stevechange/using-python-with-c-part1-3e894912214d
https://stackoverflow.com/questions/47599162/pybind11-how-to-package-c-and-python-code-into-a-singl…
https://dev.to/baduit/create-python-bindings-for-my-c-code-with-pybind11-2pck
https://medium.com/@qidi.liu.chn/how-to-use-pybind11-19aeaf4efbc1
https://youtu.be/_5T70cAXDJ0?si=TUSoRmWtg8vWj-Dy
https://youtu.be/_5T70cAXDJ0?si=7EiE-WoDuEG_mpkJ
https://pybind11.readthedocs.io/en/stable/
Python/C API/Cython
https://medium.com/@wansac/the-python-c-api-a-brief-introduction-7926ea0ef488
https://medium.com/@elniak/integrate-c-with-python-a-comprehensive-guide-for-developers-e98a1883fc7b
https://docs.python.org/3/c-api/index.html
https://medium.com/@iftimiealexandru/bridging-the-gap-between-python-and-c-c-libraries-an-explorati…
https://medium.com/@tate.wilks/writing-c-extensions-for-python-38aa0e58bf01
SWIG
https://medium.com/@howard03405vis/beginning-swig-compiler-f5c32f1c9266
https://medium.com/@som.mukhopadhyay/do-you-want-your-python-code-to-access-c-c-code-use-swig-bc972…
https://byteblog.medium.com/swig-calling-c-code-from-python-9d90ff7c9759
Boost
https://www.boost.org/doc/libs/latest/libs/python/doc/html/article.html
https://medium.com/@x10tion007/why-you-should-boost-python-with-c-wrappers-unlock-speed-and-product…




