Cython Basics: Compile Python to C
Cython is a compiler that translates Python (and Cython-annotated code) into C, which then compiles to machine code. Unlike Numba's JIT, Cython is an ahead-of-time compiler: you write code, run the Cython compiler explicitly, and get a compiled .so (Linux) or .pyd (Windows) shared library. It's the foundation for SciPy, scikit-learn, and pandas' performance-critical paths—battle-tested for two decades. Getting started requires only installing Cython and creating a .pyx file instead of .py. Your Python code compiles unchanged and usually runs 5–10× faster just from removing bytecode interpretation.
What Is Cython?
Cython is a language that sits between Python and C. A Cython file (.pyx extension) looks like Python but can include C type hints (cdef int x = 5). The Cython compiler reads your .pyx file, generates equivalent C code, and hands it to a C compiler (gcc, clang, MSVC) to produce a compiled module. You import and use it like any Python module—no callbacks to C needed. Cython removes the bytecode interpretation layer entirely: your code becomes machine instructions directly.
Cython's killer feature is incremental adoption. Valid Python is valid Cython; you add type annotations gradually to hot functions, leaving the rest in pure Python. This hybrid approach is why Cython dominates production codebases—you don't need to rewrite everything.
Install Cython and Create Your First .pyx File
Install Cython via pip:
pip install cython
Create a new directory and add a simple module:
# fibonacci.pyx
def fib_python(int n):
"""Compute the nth Fibonacci number."""
if n < 2:
return n
a, b = 0, 1
for i in range(2, n + 1):
a, b = b, a + b
return b
The function signature def fib_python(int n) tells Cython that n is a C int. Without that hint, Cython treats it as a Python object. Inside the function, Cython compiles arithmetic to native code.
Build and Import the Module
Cython uses setuptools to compile. Create a setup.py in the same directory:
# setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
name="fibonacci",
ext_modules=cythonize("fibonacci.pyx")
)
Compile in-place:
python setup.py build_ext --inplace
This generates fibonacci.c (the C source) and fibonacci.cpython*.so (or .pyd on Windows)—a compiled module you import normally:
# test_fibonacci.py
from fibonacci import fib_python
result = fib_python(100)
print(result) # Output: 354224848179261915075
That single int annotation sped up Fibonacci ~3–5× by eliminating Python object overhead. No C knowledge required.
Benchmarking: Python vs Cython
Let's measure the impact. Pure Python version:
# fib_pure.py
def fib_python(n):
"""Pure Python, no type hints."""
if n < 2:
return n
a, b = 0, 1
for i in range(2, n + 1):
a, b = b, a + b
return b
import timeit
t = timeit.timeit(lambda: fib_python(100), number=100_000)
print(f"Pure Python: {t:.3f}s")
Output:
Pure Python: 2.456s
Now the Cython version (with int type hint):
# test_cython_fib.py
from fibonacci import fib_python
import timeit
t = timeit.timeit(lambda: fib_python(100), number=100_000)
print(f"Cython: {t:.3f}s")
Output:
Cython: 0.412s
Cython is 6× faster. The speedup comes from:
- Eliminating bytecode interpretation (machine code runs directly)
- Skipping Python object creation for the return value (C
long intis used directly) - Native integer arithmetic (no dynamic dispatch to
int.__add__)
Understanding the Compilation Pipeline
When you run python setup.py build_ext --inplace, here's what happens:
- Cython compiler reads
fibonacci.pyx - Generates C code (~500 lines for a 10-line
.pyxfile) that handles type checking, reference counting, and exception unwinding - C compiler (gcc/clang/MSVC) compiles the C to object code with
-O2or-O3optimizations - Linker creates a shared object (
.so/.pyd)
You can inspect the generated C:
cython fibonacci.pyx
cat fibonacci.c | head -100
You'll see boilerplate for Python API calls, but the hot loop is pure C.
Common Pitfalls When Starting
Forgetting to rebuild after edits: Cython compiles once. If you edit fibonacci.pyx, you must re-run setup.py build_ext --inplace. The old .so is cached and won't update. Always rebuild after changes.
Mixing types in loops: Even if you declare int x, if you assign a Python object to x later, Cython falls back to Python semantics. Keep types consistent.
Not benchmarking: Don't assume type hints make code faster. Measure with timeit before and after. Sometimes the speedup is 1.2×, sometimes 50×—it depends on what the code does.
Key Takeaways
- Cython is an ahead-of-time compiler that turns Python into C code and native libraries.
- A
.pyxfile looks like Python but accepts C type hints; valid Python is valid Cython. - Add a
setup.pywithcythonize()and runbuild_ext --inplaceto compile. - Even simple type hints like
def f(int x)deliver 5–10× speedups for numerical code. - Cython is a foundation layer in SciPy, pandas, and scikit-learn—proven at scale.
Frequently Asked Questions
Do I need a C compiler to use Cython?
Yes. On Linux, install gcc or clang (apt-get install build-essential). On macOS, install Xcode Command Line Tools (xcode-select --install). On Windows, install Microsoft C++ Build Tools or Visual Studio Community (free). Cython needs a C compiler to turn its generated C code into binaries.
Can I import a Cython .pyx file directly without compiling?
No, Cython always compiles to C first. Use pyximport for development-only direct imports (it auto-compiles in the background), but production always uses explicit compilation via setup.py.
What's the difference between def and cdef in Cython?
def fib(int n) is a Python function callable from Python with C types inside. cdef int fib(int n) is a C function not callable from Python; it's only for internal use. For a function you call from Python, use def.
Does Cython release the GIL?
Not by default. To parallelize with the GIL released, add cdef functions and annotate with nogil. This is covered in Article 6 but requires more setup.
Is Cython code still Python?
Yes and no. Cython is a superset of Python: all Python code is valid Cython, but Cython code with C type hints is not valid Python. You must use the Cython compiler; a standard Python interpreter cannot run .pyx files.