第 29 章 C 扩展与互操作
29.1 ctypes 调用 C 动态库
ctypes 是 Python 标准库模块,可以直接调用 C 动态链接库,无需编写 C 代码:
import ctypes
import ctypes.util
# 加载系统 C 库
# Linux
libc = ctypes.CDLL("libc.so.6")
# macOS
libc = ctypes.CDLL("libc.dylib")
# Windows
libc = ctypes.cdll.msvcrt
# 自动查找库路径
lib_path = ctypes.util.find_library("c")
libc = ctypes.CDLL(lib_path)
# 调用 C 函数
libc.printf(b"Hello from C! %d\n", 42)
# 指定参数类型和返回类型
libc.strlen.argtypes = [ctypes.c_char_p]
libc.strlen.restype = ctypes.c_size_t
length = libc.strlen(b"Hello")
print(length) # 5
加载自定义 C 库
// mylib.c
#include <math.h>
double distance(double x1, double y1, double x2, double y2) {
return sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
}
typedef struct {
double x;
double y;
} Point;
double point_distance(Point* p1, Point* p2) {
double dx = p2->x - p1->x;
double dy = p2->y - p1->y;
return sqrt(dx*dx + dy*dy);
}
# 编译为动态库
gcc -shared -o mylib.so mylib.c -lm # Linux
gcc -shared -o mylib.dll mylib.c # Windows
gcc -shared -o mylib.dylib mylib.c -lm # macOS
import ctypes
lib = ctypes.CDLL("./mylib.so")
# 指定类型
lib.distance.argtypes = [ctypes.c_double] * 4
lib.distance.restype = ctypes.c_double
result = lib.distance(0.0, 0.0, 3.0, 4.0)
print(result) # 5.0
# 使用结构体
class Point(ctypes.Structure):
_fields_ = [
("x", ctypes.c_double),
("y", ctypes.c_double),
]
lib.point_distance.argtypes = [ctypes.POINTER(Point), ctypes.POINTER(Point)]
lib.point_distance.restype = ctypes.c_double
p1 = Point(0.0, 0.0)
p2 = Point(3.0, 4.0)
result = lib.point_distance(ctypes.byref(p1), ctypes.byref(p2))
print(result) # 5.0
ctypes 类型映射
| C 类型 | ctypes 类型 | Python 类型 |
|---|---|---|
char | c_char | bytes (1字节) |
int | c_int | int |
long | c_long | int |
float | c_float | float |
double | c_double | float |
char* | c_char_p | bytes |
void* | c_void_p | int |
int* | POINTER(c_int) | — |
数组和回调
# 数组
IntArray5 = ctypes.c_int * 5
arr = IntArray5(1, 2, 3, 4, 5)
print(list(arr)) # [1, 2, 3, 4, 5]
# 回调函数 — Python 函数作为 C 回调
COMPARE_FUNC = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)
def py_compare(a, b):
a_val = ctypes.cast(a, ctypes.POINTER(ctypes.c_int)).contents.value
b_val = ctypes.cast(b, ctypes.POINTER(ctypes.c_int)).contents.value
return a_val - b_val
# 调用 C 的 qsort
arr = (ctypes.c_int * 5)(3, 1, 4, 1, 5)
libc.qsort(arr, 5, ctypes.sizeof(ctypes.c_int), COMPARE_FUNC(py_compare))
print(list(arr)) # [1, 1, 3, 4, 5]
29.2 C 扩展模块编写基础
直接使用 Python/C API 编写扩展模块:
// fast_math.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <math.h>
static PyObject* fast_distance(PyObject* self, PyObject* args) {
double x1, y1, x2, y2;
if (!PyArg_ParseTuple(args, "dddd", &x1, &y1, &x2, &y2))
return NULL;
double dx = x2 - x1;
double dy = y2 - y1;
double result = sqrt(dx*dx + dy*dy);
return PyFloat_FromDouble(result);
}
// 方法表
static PyMethodDef FastMathMethods[] = {
{"distance", fast_distance, METH_VARARGS, "计算两点距离"},
{NULL, NULL, 0, NULL}
};
// 模块定义
static struct PyModuleDef fast_math_module = {
PyModuleDef_HEAD_INIT,
"fast_math",
"高性能数学函数",
-1,
FastMathMethods
};
// 模块初始化
PyMODINIT_FUNC PyInit_fast_math(void) {
return PyModule_Create(&fast_math_module);
}
# setup.py
from setuptools import setup, Extension
setup(
name="fast_math",
ext_modules=[
Extension("fast_math", sources=["fast_math.c"]),
],
)
pip install -e .
import fast_math
print(fast_math.distance(0, 0, 3, 4)) # 5.0
29.3 Cython 加速 Python 代码
Cython 让你用类似 Python 的语法编写 C 扩展:
# fast_fib.pyx
def fib_python(int n):
"""纯 Python 版本"""
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
# 添加 C 类型声明,获得 C 级性能
def fib_cython(int n):
"""Cython 优化版本"""
cdef long long a = 0
cdef long long b = 1
cdef long long temp
cdef int i
if n <= 1:
return n
for i in range(2, n + 1):
temp = b
b = a + b
a = temp
return b
# 类型化的函数(不能从 Python 直接调用)
cdef long long _fib_internal(int n):
cdef long long a = 0, b = 1, temp
cdef int i
for i in range(2, n + 1):
temp = b
b = a + b
a = temp
return b
# 包装为 Python 可调用
def fib_fast(int n):
return _fib_internal(n)
# setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("fast_fib.pyx"),
)
pip install cython
python setup.py build_ext --inplace
import fast_fib
print(fast_fib.fib_cython(50)) # 比纯 Python 快 10-100 倍
Cython 类型化 NumPy 数组
# fast_array.pyx
import numpy as np
cimport numpy as np
def sum_array(np.ndarray[np.float64_t, ndim=1] arr):
cdef double total = 0.0
cdef int i
cdef int n = arr.shape[0]
for i in range(n):
total += arr[i]
return total
29.4 Python/C API 简介
Python/C API 的核心概念:
// 引用计数管理
PyObject* obj = PyLong_FromLong(42); // 新引用(refcount=1)
Py_INCREF(obj); // refcount=2
Py_DECREF(obj); // refcount=1
Py_DECREF(obj); // refcount=0,对象被销毁
// 错误处理
if (result == NULL) {
// 检查是否有异常
if (PyErr_Occurred()) {
PyErr_Print(); // 打印异常
// 或
PyErr_Clear(); // 清除异常
}
}
// 设置异常
PyErr_SetString(PyExc_ValueError, "无效的参数");
return NULL;
// 类型转换
// Python → C
long val = PyLong_AsLong(obj);
double dval = PyFloat_AsDouble(obj);
const char* str = PyUnicode_AsUTF8(obj);
// C → Python
PyObject* py_int = PyLong_FromLong(42);
PyObject* py_float = PyFloat_FromDouble(3.14);
PyObject* py_str = PyUnicode_FromString("hello");
PyObject* py_list = PyList_New(0);
PyList_Append(py_list, py_int);
何时使用 C 扩展
| 方案 | 复杂度 | 性能 | 适用场景 |
|---|---|---|---|
| 纯 Python | 低 | 基准 | 大多数场景 |
| ctypes | 中 | 好 | 调用现有 C 库 |
| Cython | 中 | 很好 | 热点代码加速 |
| C 扩展 | 高 | 最好 | 极致性能需求 |
| cffi | 中 | 好 | ctypes 的现代替代 |
| pybind11 | 中 | 很好 | C++ 绑定 |
经验法则:先用 Python 写,用 cProfile 找到瓶颈,然后只对瓶颈部分用 C/Cython 加速。
本章小结:Python 与 C 的互操作是性能优化的终极武器。ctypes 适合调用现有 C 库,Cython 适合加速 Python 热点代码,原生 C 扩展适合极致性能需求。但请记住:过早优化是万恶之源,只有在确认瓶颈后才考虑 C 扩展。