Cython 加速 Python:静态类型与 C 扩展

FreeGuideOnline 最新 2026-06-16

Cython 加速 Python:静态类型与 C 扩展

为什么需要 Cython

Python 因其简洁易读的语法成为最受欢迎的编程语言之一,但解释执行的特性在某些计算密集型场景下会成为性能瓶颈。Cython 通过将类 Python 代码编译为高效 C 扩展,让你无需完全拥抱 C/C++ 就能获得接近原生代码的运行速度。本教程将带你从零开始,学会用静态类型和 C 接口为 Python 程序提速。


Cython 是什么

Cython 是一门超集语言,它在 Python 语法的基础上增加了静态类型声明、直接调用 C 函数等特性。.pyx 文件会被编译成 C 代码,然后生成 .pyd/.so 扩展模块供 Python 直接导入。本质上,Cython 让你在 Python 生态中无缝使用 C 级别的性能优化。


环境准备

安装 Cython

推荐在虚拟环境中执行:

pip install cython

同时确保系统中存在可用的 C 编译器:

  • Windows:Microsoft Visual C++ Build Tools 或 MinGW-w64
  • macOS:Xcode Command Line Tools(xcode-select --install
  • Linux:build-essential(或等价的 gcc/g++ 套装)

第一个 Cython 模块

编写 .pyx 文件

创建一个文件 hello.pyx

def say_hello(name):
    print(f"Hello, {name}!")

这看起来就是普通 Python 函数,但它会被编译为 C 扩展。

编写编译脚本 setup.py

from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize("hello.pyx")
)

编译模块

python setup.py build_ext --inplace

编译成功后,会在当前目录生成 hello.c 以及可导入的 .pyd/.so 文件。现在可以直接在 Python 中使用:

import hello
hello.say_hello("World")

核心加速手段:静态类型声明

Cython 性能飞跃的关键在于类型确定性。Python 的动态类型带来了灵活,也消耗了大量性能。为变量声明 C 类型后,Cython 会绕过大量的 PyObject 操作,生成直接操作机器数的 C 代码。

函数参数类型

def compute_sum(double a, double b):
    return a + b

循环变量的类型

.pyx 文件中使用 cdef 声明 C 变量:

def loop_sum(int n):
    cdef int i
    cdef double total = 0.0
    for i in range(n):
        total += i * 0.5
    return total

未声明的循环变量 itotal 依然是 Python 对象;使用 cdef 后,循环体将编译为纯 C 的 for 循环,速度提升明显。

整型修饰符与数组

cdef unsigned long long big_num = 10000000000
cdef char letter = 'A'
cdef float arr[100]   # C 数组,不支持负索引,需注意越界

实战:加速斐波那契数列计算

纯 Python 版本(对照)

def fib_py(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

添加静态类型(.pyx 文件)

def fib_cy(int n):
    cdef int i
    cdef long long a = 0
    cdef long long b = 1
    for i in range(n):
        a, b = b, a + b
    return a

编译并使用

通过 setup.py 构建模块后,简单对比测试:

from mymodule import fib_cy
print(fib_cy(1000000))

对于百万级迭代,fib_cy 通常比 fib_py 快几十倍。


与 C 库交互

Cython 强大的地方在于可以直接调用 C 标准库或第三方 C 函数,无需任何包装层。

调用 sin() 函数

cdef extern from "math.h":
    double sin(double x)

def c_sin(double x):
    return sin(x)

编译后,c_sin 直接调用 C 的 sin,比 math.sin(仍需 Python 调用开销)更快。

使用自定义 C 代码

可以将 C 函数定义写在 .pyx 文件的 cdef extern 块中,也可以将头文件与源文件一起编译:

# setup.py 中
ext = Extension(
    "mymodule",
    sources=["mymodule.pyx", "cmath_utils.c"],
)

释放性能的关键技巧

避免频繁的 Python 调用

循环内部如果调用了 Python 函数(如 print、列表 append),会破坏 C 级效率。尽量将循环体全部使用 C 类型操作。

使用 cpdef 生成双重接口

cpdef double square(double x):
    return x * x

cpdef 会同时生成一个 C 函数和一个 Python 包装器。在 pyx 内部调用时使用 C 接口,py 文件导入时也能用。

禁用不必要的检查

在确信安全的前提下,可以用装饰器关闭边界检查等:

import cython

@cython.boundscheck(False)  # 关闭列表/数组索引检查
@cython.wraparound(False)   # 关闭负索引处理
def fast_array_loop(double[:] arr):
    cdef int i
    cdef double s = 0.0
    for i in range(arr.shape[0]):
        s += arr[i]
    return s

double[:] 是内存视图,可零拷贝操作 NumPy 数组。


典型加速场景

  • 数值计算:大量浮点运算、积分、矩阵乘法等。
  • 循环密集型逻辑:图片像素处理、大量字符串格式化。
  • 调用已有 C/C++ 库:通过 Cython 桥接,避免手动编写 C 扩展。
  • 游戏开发、科学计算中需要兼顾开发速度和运行效率的模块。

调试与类型推断

使用注解文件

编译时添加 annotate=True 属性生成 HTML 报告:

from Cython.Build import cythonize
# 在 cythonize 中设置 annotate=True

生成的 HTML 会用黄色深浅标识“Python 交互”程度,行越黄表示越需要优化类型。

编译器指令

可以在文件头部设置全局编译器指令:

# cython: language_level=3, boundscheck=False, wraparound=False

从 Python 到 Cython 的迁移策略

  1. 直接编译:先将 .py 改为 .pyx,编译看是否正常工作。
  2. 定位热点:利用注解报告找到高 Python 交互的行。
  3. 逐步添加类型:从最内层循环变量开始添加 cdef 类型。
  4. 替换数据结构:用 C 数组或 array/memoryview 替代 Python 列表。
  5. 引入 C 函数:将核心计算替换为 C 库调用。

常见问题

编译后的模块可以跨平台吗?

编译产物 .so/.pyd 与 Python 版本、操作系统、架构强绑定,需针对目标平台进行编译。可使用 CI/CD 构建多环境 wheel 包。

Cython 和 Numba 的区别?

Numba 是 JIT 编译器,装饰器即用,适合非侵入式加速;Cython 是预编译,需要显式声明类型,但更容易集成 C 库,适合构建可扩展模块。

如何处理第三方 C 库的复杂类型?

Cython 支持声明结构体、回调函数、枚举等,通过 .pxd 声明文件可以封装复杂 C API。


小结

Cython 为你提供了一条从 Python 到 C 的平滑过渡路径。通过给变量和函数参数添加静态类型,并在编译时移除不必要的检查,大量纯 Python 代码可以获得数十甚至上百倍的性能提升。当你需要为计算密集的模块提速,或者复用现有的 C/C++ 资产,Cython 是值得掌握的重要工具。

现在打开终端,试着将你项目中最慢的一段 Python 代码改写为 .pyx 并编译——你可能会被速度的提升震撼到。