mrkeyoor.com_
Sun 20 Sept 07:02 UTC
PyPICLI & Toolingupdated 20 Sept 2026

cython review

Cython 3.3.0 translates `.pyx` files or type-annotated Python into C or C++, then hands that output to a native compiler to make a CPython extension. It is useful when a profiler points to Python object overhead in a loop, or when a package needs a Python-facing wrapper around a C library. This release understands structural pattern matching and `except*`, uses declared container item types during inference, adds Python 3.15 work, and introduces `cython.likely()` and `cython.unlikely()` branch hints. The compiler package itself had no direct dependencies in our install; the extensions it produces still create a platform-specific build and wheel obligation.

Verdict

Cython 3.3.0 installed in 0.3 seconds as one 12 MB package with no direct dependencies or audit findings, but every extension it creates still needs native builds and platform wheels. Install it for a measured Python hotspot or a C/C++ binding; skip it when the team cannot own that release matrix.

We installed it

Lab card: what happened when we installed cythonScreenshot of cython documentation
Install✓ · 0.3s1 package on disk · 12 MB
Importimport Cython in 0.08s · compiled extensions · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does cython install cleanly?

Yes. In a fresh container with an empty cache, pip install cython finished in 0.3s, leaving 1 package and 12 MB on disk. pip-audit reported no known vulnerabilities.

What does cython need to run?

Python >=3.9, and a platform wheel with compiled extensions. In our run import Cython succeeded in 0.08s, and the package ships py.typed for type checkers.

cython or numba: which should you use?

numba: Choose it for supported numerical kernels that should compile at runtime instead of entering a wheel build matrix. Cython 3.3.0 installed in 0.3 seconds as one 12 MB package with no direct dependencies or audit findings, but every extension it creates still needs native builds and platform wheels.

When should you not use cython?

No measured hotspot exists yet. Cython cannot remove Python overhead that remains dynamic, and compilation alone is not a speed diagnosis.

API stability4/5Cython 3 keeps the familiar `.pyx`, `.pxd`, pure-Python annotation, extension-type, typed-memoryview, and `cythonize()` surfaces. Version 3.3.0 still changes compiler output in meaningful ways: declared container types now feed inference, fused C functions have a new export naming scheme, and `prange` gained free-threading behavior. Pin the build compiler and treat generated-code changes as release changes even when Python call sites stay the same.
Docs4/5The official manual has runnable routes for `.pyx` and pure-Python mode, C and C++ wrapping, extension types, typed memoryviews, NumPy, OpenMP, compiler directives, debugging, and packaging. Its annotation report explains where Python C-API work remains. Production answers are sometimes split between the user guide and build-system pages, while compiler, linker, and ABI errors necessarily depend on the platform toolchain rather than Cython alone.
Maintenance5/5Release 3.3.0 shipped on 2026-08-22, and the repository was pushed again on 2026-08-25. The release added current Python syntax, Python 3.15 preparation, free-threading work, Limited API changes, optimizer improvements, and targeted correctness fixes. GitHub reports 1,512 open issues and pull requests, which is a substantial queue, but the release cadence and breadth of current-runtime work show active compiler maintenance.
Ecosystem5/5Cython is used as build tooling across scientific Python and native-wrapper projects, and the repository has 10,832 stars. Standard Python packaging can isolate it under `build-system.requires`, while cibuildwheel and common CI providers cover the wheel workflow around it. The surrounding tooling does not erase project-specific duties: authors still choose compiler flags, external headers, ABI targets, and the exact Python and platform wheel set they promise to support.

Use it if

  • Profiling has identified a loop where C scalars, typed memoryviews, or fewer Python calls can change the result.
  • You need to present a C or C++ API as normal Python functions and extension types.
  • A CPU-bound section can release the GIL, and your build can supply OpenMP when `prange` is justified.
  • Your release process already builds and tests wheels across every supported Python, OS, architecture, and threading variant.
Skip it if

Setup reality

We installed Cython 3.3.0 in a fresh Python 3.12 Bookworm container in 0.3 seconds. It left one package using 12 MB, and import Cython completed in 0.08 seconds. pip-audit reported zero known vulnerabilities. The wheel had no direct dependencies, included compiled .so files and py.typed, required Python 3.9 or newer, and declared Apache-2.0. Those figures cover the compiler, not any extension you build with it.

Declare Cython under [build-system].requires, then make cythonize() produce sources for setuptools or another backend. A Linux source build needs a C compiler and Python development headers. Windows needs compatible MSVC tools; macOS needs the command-line developer tools. Wrapping C++ adds header paths, library paths, linker names, and ABI compatibility. Shipping generated C removes Cython from an sdist consumer's build environment, but it does not remove the native compiler.

A changed .pyx file can coexist with an older extension in an editable environment. Rebuild, then inspect the imported module's __file__ when results look stale. NumPy's C API requires NumPy in the isolated build requirements and its include directory in the extension configuration. A typed memoryview can consume a compatible buffer without importing NumPy's C API. In 3.3.0, annotations such as list[float] now affect inference, so compare annotation reports and tests after upgrading.

nogil only permits code that avoids Python objects until the GIL is reacquired. prange needs OpenMP compiler and linker flags; Apple's default toolchain does not provide the usual OpenMP setup. Cython 3.3.0 also allows prange() without releasing the GIL for free-threaded builds, which is a different concurrency choice from nogil=True. Keep unsafe compiler directives on small tested functions, and install each finished wheel into a clean environment before publishing it.

Patterns

Build one extension module compile-pyx

# counter.pyx
def count_up(int stop):
    cdef int i, total = 0
    for i in range(stop):
        total += i
    return total

# setup.py
from setuptools import Extension, setup
from Cython.Build import cythonize
setup(ext_modules=cythonize([Extension('counter', ['counter.pyx'])], language_level=3))

`cythonize()` generates C first; the platform compiler then creates the importable extension. The `Extension` name controls its Python import path.

Isolate the compiler dependency pin-build-tool

# pyproject.toml
[build-system]
requires = ["setuptools>=69", "wheel", "Cython==3.3.0"]
build-backend = "setuptools.build_meta"

Cython belongs in isolated build requirements when runtime code imports only the compiled module. A pin keeps generated C consistent across builds.

Annotate importable Python source use-pure-python-syntax

import cython

@cython.cfunc
def between(value: cython.int, low: cython.int, high: cython.int) -> cython.int:
    if value < low:
        return low
    if value > high:
        return high
    return value

The shadow `cython` module lets the file run in CPython before compilation. A `cfunc` is C-only once compiled, so add a `def` wrapper for Python callers.

Generate an annotation report inspect-overhead

cython -3 -a counter.pyx
# Open counter.html in a browser.

The generated HTML colors lines by Python interaction. Darker yellow points to more C-API work; it is evidence for another type change, not a benchmark.

Read a contiguous typed buffer sum-buffer

# cython: boundscheck=False, wraparound=False
def sum_values(double[::1] values):
    cdef Py_ssize_t i
    cdef double total = 0
    for i in range(values.shape[0]):
        total += values[i]
    return total

`double[::1]` requires a contiguous buffer with a matching element format. Disabled bounds and wraparound checks make a bad index unsafe.

Keep Python work outside a GIL-free loop release-gil

cdef double raw_total(double[::1] values) noexcept nogil:
    cdef Py_ssize_t i
    cdef double total = 0
    for i in range(values.shape[0]):
        total += values[i]
    return total

def total(double[::1] values):
    with nogil:
        return raw_total(values)

A `nogil` block cannot use ordinary Python objects. Validate and convert inputs before entering it, and state native exception behavior explicitly.

Reduce values with OpenMP parallel-reduction

from cython.parallel cimport prange

def parallel_total(double[::1] values):
    cdef Py_ssize_t i
    cdef double total = 0
    for i in prange(values.shape[0], nogil=True, schedule='static'):
        total += values[i]
    return total

`prange` needs OpenMP at compile and link time. Writes that are not recognized reductions need synchronization or they can race.

Declare a function from a C header call-c-library

cdef extern from "math.h":
    double sqrt(double value) nogil

def square_root(double value):
    if value < 0:
        raise ValueError('value must be non-negative')
    return sqrt(value)

The `extern` block describes a symbol; it does not locate third-party headers or libraries. Add those paths and linker names to the extension build.

Own a C++ vector from Python wrap-cpp-class

# distutils: language = c++
from libcpp.vector cimport vector

cdef class Numbers:
    cdef vector[int] values
    def append(self, int value):
        self.values.push_back(value)
    def __len__(self):
        return self.values.size()

C++ mode changes the compiler and linker path. Exceptions crossing the boundary need declarations such as `except +` on external C++ methods.

Mark an expected branch in 3.3 hint-branch

import cython

@cython.cfunc
def divide(total: cython.double, count: cython.long) -> cython.double:
    if cython.unlikely(count == 0):
        return 0.0
    return total / count

`cython.likely()` and `cython.unlikely()` arrived in 3.3. They guide the C compiler; benchmark the real branch distribution before keeping the hint.

Alternatives

PackageRegistryPick it when
numbaPyPIChoose it for supported numerical kernels that should compile at runtime instead of entering a wheel build matrix.
pybind11PyPIChoose it when the binding layer belongs in modern C++ and template-based wrappers fit the codebase.
cffiPyPIChoose it for C interfaces where ABI or API declarations matter more than compiling Python-shaped source.
mypyPyPIUse its mypyc compiler when ordinary type annotations and mypy compatibility matter more than Cython-specific declarations.

More cli & tooling guides

commander · chalk · typescript · esbuild · yargs · click · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.