Python#
This Chapter has some advanced concepts when working with Python.
Code Wrapping#
Python is able to run code written in many other languages, provided they provide a suitable interface or ‘wrapper’. In particular it is common for C or C++ code to be wrapped with Python.
There are several approaches that can be taken with wrapping, the most basic is to use the built-in ctypes Foreign Function library. This is useful if your library has a basic C interface and you are comfortable with low-level APIs.
The cffi library also targets C APIs and provides a higher level interface, although it requires an extra third-party module dependency.
Cython is a Python language superset with extra functionality for C/C++ interactions. The interaction code is similar to Python.
SWIG is a general wrapping interface for C and C++, supporting many target languages at once.
nanobind is a wrapping library focused on Python and C++, it allows wrapping of advanced interfaces and data types with high performance. It is a successor of pybind11, which itself built on many of the ideas of Boost Python.
The ichello ICHEC project is an end-to-end example of Python wrapping with nanobind
.
Packaging#
Packaging wrapped code is more involved than native or vanilla Python, since compilation of the C/C++ code needs to be managed and binary redistributable packages need to be created for all supported systems, rather than a single source redistributable.
The CMake build system is often used to manage building the C and C++ code. This can be driven via a basic setup.py
script pointed to by the pyproject.toml
Python project description, however it may be easier to use a dedicated tool for wrapped Python projects. scikit-build-core, a recent successor to Scikit Build
is becoming popular for this purpose. It can be used as a build-system
in the pyproject.toml
and will automatically handle CMake integration and binary generation.
In addition to the ichello ICHEC project the Nanobind Official Scikit Build example may be a useful reference for getting started.
Development#
Combined development of the Python and C/C++ code can be awkward. If a project is primarily focused on the C/C++ element it may be worth pulling it in from a separate repo - for example as a CMake External Project and keeping only interfacing code in the Python repo.
If you are working on or troubleshooting a combined Python and C++ project you will likely want to use an editrable install without build isolation - meaning you will need to install dependency packages explicitly as follows:
pip install -e . --no-build-isolation
Debugging and Profiling#
Debugging wrapped code can be tricky. If using the lldb
debugger on Mac and a Python virtual environment you will need to set settings set target.process.stop-on-exec false
to avoid it exiting early if Python interpreters are called in a chain.
Futher Reading#
Building an Interpreter#
Some scenarios where you might end up building a Python interpreter include:
Building Python bindings on a system without root and missing Python headers
Distributing an application with a bundled interpreter
Advanced performance analysis or debugging
CPython is the most typical interpreter to build. To do so you can do:
git clone https://github.com/python/cpython.git
You probably want to check out a branch corresponding to a release version:
cd cpython
git checkout 3.9
Then you can do:
./configure --prefix $INSTALL_DIR --enable-shared
make -j 4
make install
installing in $INSTALL_DIR
otherwise you will break your system. You can look at configure --help
for other options that may be of interest - such as enabling profiling.