A modern, production-ready template repository for building high-performance Python C Extensions with C++23 and nanobind.
This repository is designed to help developers quickly bootstrap, compile, test, and package Python extensions written in C++ without the boilerplate. It uses modern tooling such as scikit-build-core and nanobind, offering significant binary size and compilation speed improvements over legacy tools like pybind11.
- Modern C++23 Base: Targets standard C++23 for performance and clean modern coding practices.
- nanobind Integration: Leverages efficient binding mechanics with zero-copy NumPy array exchanges.
- Cheminformatics Reference Example: Includes a simple implementation of an XYZ Chemical File Parser showing OOP class mappings, STL container bindings, memory reference policies, and Python-side utility layers.
- Task Runner: Automates routine tasks (formatting, linting, building, and testing) via a zero-dependency Python-based helper runner (pymake).
- Cross-Platform Compilation: Supports Windows, macOS, and Linux out of the box with
scikit-build-coreand conda environments.
python-c-extension-template/
├── src/ # C++ Source Code & Bindings
│ ├── Atom.hpp # Definition of the Atom C++ structure
│ ├── Molecule.hpp # Declaration of the Molecule C++ parser class
│ ├── Molecule.cpp # Parsing and calculation implementations
│ ├── ext.cpp # Module binding definition (nanobind mapping)
│ └── CMakeLists.txt # C++ build settings (CMake targets)
├── python_c_ext/ # Python Package Boundary
│ ├── _ext.pyi # Autogenerated stub files for IDE type autocompletion
│ ├── __init__.py # Main package exposure
│ └── xyz_utils.py # Python-side helper library (NumPy coordinate/distance calculations)
├── tests/ # Test Suite
│ └── test_xyz_parser.py # pytest suite validating C++ bindings and Python integration
├── pyproject.toml # PEP 517/518 build system config (scikit-build-core)
├── pymakefile.py # Automated tasks runner script
├── pymake.bat / .sh # Platform-specific entrypoint scripts for automated tasks
├── environment.yml # Development environment specification
└── README.md # Project documentation
The template contains a simple chemical file parser example under the ext namespace:
- C++ Layer (
Atom,Molecule): Implements structured files parsing (standard XYZ coordinate files), geometric center (center of mass) computation, and case-sensitive elemental filtering in native C++. - Binding Layer (
ext.cpp): Maps C++ types to Python classes. Demonstrates:- STL automatic mappings (
std::stringandstd::vector) via<nanobind/stl/...>headers. - Internal reference lifetime management (
nb::rv_policy::reference_internal) on atom lists. - Python property mapping (
def_rw,def_prop_ro) representing C++ fields and getter methods.
- STL automatic mappings (
- Python Utility Layer (
xyz_utils.py): Demonstrates seamless integration with Python scientific libraries, transforming the C++ coordinates list into a 2D NumPy array with zero-copy conversion, and performing rapid pairwise distance matrix calculations.
- Miniforge3 (or another Conda distribution) installed on your system.
To develop or run the extension locally, you must create a .env_path file in the project root directory. This file should contain exactly one line: the absolute path to the root directory of the conda environment you wish to use.
Windows:
C:\Users\username\miniconda3\envs\python_c_ext_dev
macOS:
/Users/username/miniconda3/envs/python_c_ext_dev
Linux:
/home/username/miniconda3/envs/python_c_ext_dev
This project uses a standalone Python task runner called pymake to automate development tasks. It provides a make-like experience using only the Python standard library.
Use the platform-specific wrapper scripts to run tasks:
- Windows:
.\pymake.bat <task> [args] - Linux/macOS:
./pymake.sh <task> [args]
To see all available tasks and their options, run:
.\pymake.bat --help| Task | Description |
|---|---|
format |
Format the Python codebase with Ruff. |
lint |
Run static analysis (linting) on Python code with Ruff. |
format_cpp |
Format C++ code with clang-format. |
lint_cpp |
Run style linting on C++ code (check-only mode). |
test |
Execute the test suite with pytest. |
build_whl |
Build the wheel file directly using the Conda environment (cross-platform). |
package |
Package the project using conda-pack. |
Example: Run the test suite:
.\pymake.bat test verbose=trueIf you are compiling the CMake project manually or using an IDE (such as CLion or VS Code), configure the following CMake variables pointing to your conda environment paths:
Python_ROOT_DIR: Root directory of the conda environment.CMAKE_PREFIX_PATH: Paths to environment library directories (for dependency resolution).NUMPY_INCLUDE_DIR: Include directory path for NumPy headers.
-DPython_ROOT_DIR="C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev"
-DCMAKE_PREFIX_PATH="C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev;C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev\Library;C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev\Lib\site-packages"
-DNUMPY_INCLUDE_DIR="C:\Users\YourUsername\Miniforge3\envs\python_c_ext_dev\Lib\site-packages\numpy\_core\include"