首页 / 知识库 / 0基础入门-阅读资料 / 0基础-python入门到精通

第 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 类型
charc_charbytes (1字节)
intc_intint
longc_longint
floatc_floatfloat
doublec_doublefloat
char*c_char_pbytes
void*c_void_pint
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 扩展最好极致性能需求
cffictypes 的现代替代
pybind11很好C++ 绑定

经验法则:先用 Python 写,用 cProfile 找到瓶颈,然后只对瓶颈部分用 C/Cython 加速。


本章小结:Python 与 C 的互操作是性能优化的终极武器。ctypes 适合调用现有 C 库,Cython 适合加速 Python 热点代码,原生 C 扩展适合极致性能需求。但请记住:过早优化是万恶之源,只有在确认瓶颈后才考虑 C 扩展。