Let's check it out!
pybind11
A lightweight header-only library that can be used to integrate C++ with Python to create bindings exposing C++ functions to Python. Client code written in Python can be consumed to invoke the underlying C++ code.
Installation
All examples here are executed on Ubuntu Linux. Therefore install pybind11 globally to begin the examples:
sudo apt-get update sudo apt-get install pybind11-dev sudo apt install build-essential g++ |
Example I
Create an example that exposes C++ function to Python with pybind11. Launch terminal | Enter commands:
mkdir -p ~/HelloPyBind cd HelloPyBind python -m venv .venv source .venv/bin/activate # OR .\.venv\Scripts\activate which python `which python` --version # Python 3.8.10 pip install pybind11 pip install --upgrade pip |
Create the following files: example.cpp, setup.py, test.py. Enter the following C++ and Python source code:
| example.cpp |
#include <pybind11/pybind11.h>
int add(int x, int y)
{
return x + y;
}
PYBIND11_MODULE(example, m)
{
// optional module docstring
m.doc() = "pybind11 example plugin";
m.def("add", &add, "A function which adds two numbers");
}
|
| setup.py |
from setuptools import setup, Extension import pybind11 ext_modules = [ Extension( "example", ["example.cpp"], include_dirs=[pybind11.get_include()], language="c++" ), ] setup( name="example", version="0.1", ext_modules=ext_modules, ) |
| test.py |
import example
result = example.add(1, 2)
print(f"1 + 2 = {result}")
|
Build C++ code using setup.py build inplace. Finally execute python test.py for Python to execute C++ code!
python setup.py build_ext --inplace # example.cpython-38-x86_64-linux-gnu.so python test.py # OUTPUT 1 + 2 = 3 |
Example II
Repeat previous exercise but prefer PyCharm IDE. Launch PyCharm | New Project. Enter the following info:
| Location: | ~/HelloPyBind |
| Interpreter type: | uv |
| Python version: | 3.11 |
| Path to uv: | ~/.local/bin/uv |
PyCharm should setup UV virtual environment and configure Python interpreter if not then enter commands:
uv venv --python 3.11 source .venv/bin/activate # OR .\.venv\Scripts\activate which python `which python` --version # Python 3.11.11 |
In the PyCharm Terminal | Enter the following commands for UV to install and sync package dependencies:
uv add pybind11 uv add setuptools |
uv lock uv sync |
Create the following files: example.cpp, setup.py, test.py. Enter C++ and Python code similar to Example I. Build C++ code using setup.py build install. Finally execute uv run test.py for Python to execute C++ code!
uv run setup.py build uv run setup.py install # example.cpython-38-x86_64-linux-gnu.so uv run test.py # OUTPUT 3 + 5 = 8 9 - 5 = 4 |
Example III
Repeat previous exercise but prefer CMake to build C++ code via CMakeLists.txt. Create PyCharm Project:
| Location: | ~/HelloPyBind |
| Interpreter type: | uv |
| Python version: | 3.11 |
| Path to uv: | ~/.local/bin/uv |
In the PyCharm Terminal | Enter the following commands for UV to install and sync package dependencies:
uv add pybind11 |
uv sync |
Create the following files: example.cpp, CMakeLists.txt, test.py. Enter code similar to Example II but update:
| CMakeLists.txt |
cmake_minimum_required(VERSION 3.16)
project(example)
# Find the Python 3.11-specific pybind11 CONFIG from pip
execute_process(
COMMAND ${Python3_EXECUTABLE} -m pybind11 --cmakedir
OUTPUT_VARIABLE pybind11_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
find_package(pybind11 REQUIRED CONFIG)
pybind11_add_module(example example.cpp)
|
Build C++ code using cmake and make. In the PyCharm Terminal | Enter the following commands to build:
mkdir -p build cd build |
cmake -DPython3_EXECUTABLE=$(which python) .. make -j$(grep -c ^processor /proc/cpuinfo) |
Enter commands to copy library to be used. Finally execute uv run test.py for Python to execute C++ code!
python -c "import sysconfig, shutil, glob; \
dst = sysconfig.get_paths()['platlib']; \
so = glob.glob('*.so')[0]; \
shutil.copy2(so, dst)"
cd ..
uv run test.py # OUTPUT Hello, World!
|
IMPORTANT
The first 3x examples worked but tightly coupled Python and C++ without being able to debug separately!
Example IV
Repeat previous exercise but prefer to modify the project layout to separate top level Python and C++ code:
~/HelloPyBind/ ├── cpp/ │ ├── src/ │ │ ├── api/ │ │ │ ├── my_api.h │ │ │ └── my_api.cpp │ │ ├── bindings/ │ │ │ └── pybind_module.cpp # pybind11 bindings │ │ ├── CMakeLists.txt │ │ └── main.cpp # C++ executable entry point │ ├── tests/ │ │ ├── CMakeLists.txt │ │ └── test_api.cpp │ └── CMakeLists.txt # top-level C++ (CLion entry point) │ └── python/ ├── .venv/ │ └── lib/ │ └── python3.11/ │ └── site-packages/ │ └── my_api_py.cpython-311-x86_64-linux-gnu.so ├── test.py ├── pyproject.toml └── README.md |
Create PyCharm Project. Setup virtual environment as before then create CLion project to build C++ code.
| Location: | ~/HelloPyBind/python |
| Interpreter type: | uv |
| Python version: | 3.11 |
| Path to uv: | ~/.local/bin/uv |
Launch CLion | New Project. Create C++ Executable using C++ 17. Enter the following CLion information:
| C++ | C++ Executable |
| Location: | ~/HelloPyBind/cpp |
| Language standard: | C++17 |
Set build directory in CLion. File menu | Settings... | Build, Execution, Deployment | CMake | Build directory
Setup folder layout as above. Enter all C++ source code and tests. Rebuild entire solution in Debug mode.
IMPORTANT
CMakeLists.txt files are configured to copy shared object SO file into the Python .venv virtual environment
Launch PyCharm | Complete the test runner. Finally execute uv run test.py for Python to execute C++ code!
uv run test.py # OUTPUT 1 + 2 = 3 |
Example V
Repeat previous exercise but prefer more complexity to build C++ code with classes as consumed by Python
Create PyCharm Project. Setup virtual environment as before then create CLion project to build C++ code. Launch CLion | New Project. Create C++ Executable using C++ 17. Enter the following CLion from before.
Set build directory in CLion. File menu | Settings... | Build, Execution, Deployment | CMake | Build directory. Setup folder layout as above. Enter all C++ source code and tests. Rebuild entire solution in Debug mode.
Launch PyCharm | Complete the test runner. Finally execute uv run test.py for Python to execute C++ code!
uv run test.py # OUTPUT # Guitar: 'Fender' [6-string] = $1500.0 # Guitar: 'Ibanez' [7-string] = $1200.0 # Guitar: 'Gibson' [6-string] = $2400.0 |
Example VI
Repeat previous exercise but prefer more complexity to build C++ code with templates consumed by Python
Create PyCharm Project. Setup virtual environment as before then create CLion project to build C++ code. Launch CLion | New Project. Create C++ Executable using C++ 17. Enter the following CLion from before.
Set build directory in CLion. File menu | Settings... | Build, Execution, Deployment | CMake | Build directory. Setup folder layout as above. Enter all C++ source code and tests. Rebuild entire solution in Debug mode.
Launch PyCharm | Complete the test runner. Finally execute uv run test.py for Python to execute C++ code!
# OUTPUT # Container[0] = 0.0 # Container[1] = 1.0 # Container[2] = 2.0 # Container[3] = 3.0 # Container[4] = 4.0 |
# OUTPUT # Container[5] = 5.0 # Container[6] = 6.0 # Container[7] = 7.0 # Container[8] = 8.0 # Container[9] = 9.0 |
Example VII
Repeat previous exercise but prefer Visual Studio 2022 on Windows to build an increasing C++ code base:
~/HelloPyBind/ ├── cpp/ │ ├── src/ │ │ ├── core/ # Core API implementation │ │ │ ├── *.h │ │ │ └── *.cpp │ │ ├── math/ # Math-related API │ │ │ ├── *.h │ │ │ └── *.cpp │ │ ├── mesh/ # Mesh-related API │ │ │ ├── *.h │ │ │ └── *.cpp │ │ ├── bindings/ │ │ │ └── pybind_module.cpp # pybind11 bindings │ │ ├── CMakeLists.txt │ │ └── main.cpp # C++ executable entry point │ ├── tests/ │ │ ├── CMakeLists.txt │ │ ├── test_matrix.cpp │ │ ├── test_vector.cpp │ │ ├── test_mesh.cpp │ │ ├── test_mesh_algorithms.cpp │ │ └── test_mesh_processor.cpp │ └── CMakeLists.txt # top-level C++ (CLion entry point) └── python/ |
Launch Visual Studio 2022 | Continue without code. File | Open | CMake... Navigate to cpp/CMakeLists.txt. Build menu | Build All. Finally, choose Test menu | Test Explorer. Run All Tests in View or choose to Debug:
Summary
To summarize, we have demonstrated various PyBind examples in which C++ library code is consumed by a single Python API exclusively. However, in future there may be instances in which the C++ library may need to be consumed by multiple languages. In this case ctypes.cdll.LoadLibrary() may be better that PyBind!
