Calling C from Python
When you need maximum performance or need to use existing C/C++ libraries, Python provides several ways to interface with C code.
1. ctypes — Call Compiled Libraries
ctypes is in the standard library and lets you call functions in shared libraries (.so / .dll) directly.
import ctypes
# Load a shared library
lib = ctypes.CDLL("./mylib.so") # Linux/macOS
# lib = ctypes.CDLL("mylib.dll") # Windows
# libc = ctypes.CDLL("libc.so.6") # system libc
# Call a simple C function
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_int
result = lib.add(3, 4) # 7
// mylib.c → compile with: gcc -shared -fPIC -o mylib.so mylib.c
int add(int a, int b) {
return a + b;
}
Common ctypes Types
| C type | ctypes |
|---|---|
int |
c_int |
double |
c_double |
char * |
c_char_p |
void * |
c_void_p |
| pointer | POINTER(type) |
2. cffi — C Foreign Function Interface
More ergonomic than ctypes; define the C API as a string.
from cffi import FFI
ffi = FFI()
ffi.cdef("""
int add(int a, int b);
""")
lib = ffi.dlopen("./mylib.so")
lib.add(3, 4) # 7
3. Python C Extension (C API)
Write a C module that Python imports natively — maximum performance and control.
// mymodule.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject* py_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", py_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);
}
# setup.py
from setuptools import setup, Extension
setup(ext_modules=[Extension("mymodule", sources=["mymodule.c"])])
# Build: python setup.py build_ext --inplace
import mymodule
mymodule.add(3, 4) # 7
4. Cython
Write Python-like code with C type annotations; compiles to C, then to a shared library.
# mymodule.pyx
def add(int a, int b) -> int:
return a + b
cython mymodule.pyx # generates mymodule.c
gcc -shared -fPIC ... -o mymodule.so mymodule.c
Used heavily by NumPy, Pandas, scikit-learn.
5. Quick Comparison
| Method | Ease of Use | Performance | Use Case |
|---|---|---|---|
ctypes |
Easy | Good | Use existing .so/.dll |
cffi |
Easy | Good | Use existing C libraries |
| C Extension | Complex | Best | Performance-critical code |
| Cython | Medium | Very good | Accelerate Python code |
| SWIG | Medium | Good | Wrap large C/C++ libraries |
Key Interview Points
ctypesandcfficall pre-compiled libraries without writing C; great for wrapping third-party libs.- Python C Extensions give the most control and best performance, but require understanding the CPython C API (reference counting,
Py_INCREF/Py_DECREF). - Reference counting bugs in C extensions can cause memory leaks or segfaults.
- Cython is the pragmatic choice when you want to accelerate existing Python code with minimal rewrite.
- The GIL must be considered: C extensions can release it with
Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADSto allow true parallelism. - NumPy, Pandas, and most scientific Python libraries are built on C extensions for performance.