c2py23
Zero-copy C99 function wrapping for Python 2.7 through 3.15.
One compiled .so works everywhere -- no recompilation, no #include <Python.h>,
no numpy dependency.
GraalPy, PyPy, or debugging? See docs/pythonh.md for
standard CPython extension that includes <Python.h> directly.
See --pythonh mode.
How It Works
- Write a
.c2pyinterface file describing your C function signatures, buffer formats, and dispatch conditions - Run
c2py23 mymod.c2py -o mymod_wrapper.cto generate the wrapper - Compile with any C99 compiler:
cc -shared ... mymod_wrapper.c ... -o mymod.so - Import the resulting
.sofrom Python and call your C functions with ctypes arrays, memoryviews, or any buffer-protocol object
Key Features
- Zero-copy: raw C pointers from Python buffers, no allocation in wrappers
- One
.soeverywhere: Python 2.7 through 3.15, Linux/Windows/macOS/aarch64 - Format dispatch: route to different C functions based on buffer type (
floatvsdoublevsint32...) - CPU feature dispatch: compile AVX-512/AVX2/scalar variants, select at runtime
- GIL release: release the GIL during pure-C computation
- Free-threading: opt in to
python3.14ttrue parallelism - No heap allocation in generated wrappers -- all memory owned by Python
What It Does NOT Do
- No copies -- the wrapper never reallocates or copies buffer data
- No numpy dependency -- uses PEP 3118 buffer protocol directly
- No GPU kernels -- CPU-only buffer access (see design decisions)
- No complex type -- use interleaved float pairs in float buffers
- No keyword arguments -- positional-only, matching C calling conventions
Quick Example
# arraysum.c2py
{
"module": "arraysum",
"source": ["arraysum.c"],
"functions": [
{
"py_sig": "add_arrays(a: buffer, b: buffer, out: buffer) -> void",
"checks": [
"a.format == 'd'",
"b.format == 'd'",
"out.format == 'd'",
"a.n == b.n",
"b.n == out.n",
],
"c_overloads": [
{
"sig": "add_d(const double *a, const double *b, int n, double *out)",
"map": {
"a": "a.ptr",
"b": "b.ptr",
"n": "a.n",
"out": "out.ptr",
},
},
],
},
],
}
import ctypes, arraysum
a = (ctypes.c_double * 3)(1.0, 2.0, 3.0)
b = (ctypes.c_double * 3)(4.0, 5.0, 6.0)
out = (ctypes.c_double * 3)()
arraysum.add_arrays(a, b, out)
# out == [5.0, 7.0, 9.0]
Where to Go Next
- Getting Started -- install and build your first module
- Building Extensions -- cmake, meson, setuptools, wheel packaging
- User Guide -- thread safety, timing, packaging
- Specification -- full grammar and architecture
- Examples -- worked examples with live test output