How to extend Python with C/C++ Code

Python’s simplicity comes at a performance cost. Learn how to extend Python with C/C++ for critical code paths.

Introduction

Python is beloved for its readability and ease of use, but interpreted languages have inherent performance limitations. When you need maximum speed for compute-intensive operations, extending Python with C or C++ is a powerful solution.

When to Use C Extensions

  • CPU-bound computations: Mathematical operations, algorithms
  • Interfacing with C libraries: Using existing C/C++ code
  • Performance-critical paths: When Python becomes a bottleneck
  • Memory-intensive operations: More control over memory management

Method 1: Python C API

The traditional approach using Python’s C API:

Example: A simple add function

#include <Python.h>

static PyObject* add(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return PyLong_FromLong(a + b);
}

static PyMethodDef methods[] = {
    {"add", add, METH_VARARGS, "Add two integers"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "mymodule",
    NULL,
    -1,
    methods
};

PyMODINIT_FUNC PyInit_mymodule(void) {
    return PyModule_Create(&module);
}

Build with setup.py

from setuptools import setup, Extension

module = Extension('mymodule', sources=['mymodule.c'])

setup(
    name='mymodule',
    ext_modules=[module]
)
python setup.py build_ext --inplace

Method 2: Cython

Cython provides a more Pythonic approach:

cython_example.pyx

def add(int a, int b):
    return a + b

def fast_sum(double[:] arr):
    cdef double total = 0
    cdef int i
    for i in range(arr.shape[0]):
        total += arr[i]
    return total

setup.py for Cython

from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize("cython_example.pyx")
)

Method 3: ctypes

For interfacing with existing shared libraries:

import ctypes

# Load the library
lib = ctypes.CDLL('./mylib.so')

# Define argument and return types
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_int

# Call the function
result = lib.add(5, 3)

Method 4: pybind11

Modern C++ binding with pybind11:

#include <pybind11/pybind11.h>

int add(int a, int b) {
    return a + b;
}

PYBIND11_MODULE(mymodule, m) {
    m.def("add", &add, "Add two integers");
}

Performance Comparison

Method Ease of Use Performance Use Case
Python C API Low Highest Full control
Cython Medium High Numeric code
ctypes High Medium Existing libs
pybind11 High High C++ integration

Conclusion

Choose the right tool based on your needs:

  • pybind11: Best for new C++ code
  • Cython: Great for optimizing Python code
  • ctypes: Quick integration with existing libraries
  • C API: Maximum control and performance