Building a High-Performance C Extension for Python¶
Python is beloved for its readability and ease of use, but it isn't always the fastest tool for heavy numerical lifting. When you hit a performance bottleneck, you can drop down into C to handle the "heavy lifting" while keeping your high-level logic in Python.
This guide walks you through creating a C extension from scratch.
1. The Architecture of a C Extension¶
Before writing code, it helps to understand how Python (written in C) interacts with your custom C code. Every extension requires a specific boilerplate structure to "handshake" with the Python interpreter.
The 4 Pillars of a C Extension:¶
- The Header: Including
<Python.h>to access the Python API. - The Logic: Your standard C functions.
- The Mapping: A method table that maps C functions to Python names.
- The Initialization: A function that registers the module when you call
import.
2. Writing the C Source (power.c)¶
Let’s create a module called power_tools that calculates the square of a number. This is a simple example, but the pattern scales to complex algorithms.
#include <Python.h>
/**
* 1. The Method Function
* This is where the work happens.
* 'self' points to the module object.
* 'args' contains the arguments passed from Python.
*/
static PyObject* method_square(PyObject* self, PyObject* args) {
double value;
// Parse the Python arguments into C types
// "d" tells Python to expect a double (float)
if (!PyArg_ParseTuple(args, "d", &value)) {
return NULL; // Raises a TypeError in Python
}
double result = value * value;
// Convert the C double back into a Python Float object
return PyFloat_FromDouble(result);
}
/**
* 2. The Method Table
* Tells Python which C functions are available.
*/
static PyMethodDef PowerMethods[] = {
{"square", method_square, METH_VARARGS, "Returns the square of a number."},
{NULL, NULL, 0, NULL} // Sentinel
};
/**
* 3. The Module Definition
*/
static struct PyModuleDef powertools_module = {
PyModuleDef_HEAD_INIT,
"power_tools", // Module name
"A module for fast power calculations.",
-1,
PowerMethods
};
/**
* 4. The Initialization Function
* Must be named PyInit_<modulename>
*/
PyMODINIT_FUNC PyInit_power_tools(void) {
return PyModule_Create(&powertools_module);
}
3. The Build Script (setup.py)¶
Compiling C for Python manually is tedious. Python’s setuptools handles the compiler flags, include paths, and linking for you.
from setuptools import setup, Extension
# Define the extension module
ext_module = Extension(
"power_tools",
sources=["power.c"]
)
setup(
name="PowerTools",
version="1.0",
description="C Extension Tutorial",
ext_modules=[ext_module],
)
4. Compiling and Using the Module¶
Step A: Compile¶
Run the following in your terminal. The --inplace flag puts the compiled .so (Linux/Mac) or .pyd (Windows) file in your current directory.
Step B: Test in Python¶
Now, you can import it like any other library: