大家好,我是正在实战各种 AI 项目的程序员晚枫。

用 C 编写 Python 模块,突破性能瓶颈!NumPy、Pandas 为什么这么快?答案就在这一讲。


📖 开篇:为什么需要 C 扩展?

Python 很快,但不是所有场景都快:

1
2
3
4
5
6
7
8
# Python 循环:慢
total = 0
for i in range(10**7):
total += i * i

# NumPy:快!底层是 C 实现
import numpy as np
total = np.sum(np.arange(10**7) ** 2)

NumPy、Pandas、TensorFlow 都是 C/C++ 扩展的典型例子。


🔧 开发环境准备

1
2
3
4
5
6
7
8
9
# 安装编译工具
# macOS
xcode-select --install
brew install python3-dev

# Ubuntu/Debian
sudo apt install python3-dev build-essential

# Windows: 安装 Visual Studio Build Tools

📝 最简单的 C 扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// mymodule.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>

// 函数签名必须是 PyObject* 函数名(PyObject* self, PyObject* args)
// 参数通过 PyArg_ParseTuple 解析
static PyObject* hello(PyObject* self, PyObject* args) {
char* name;
// "s" 表示解析一个字符串参数
if (!PyArg_ParseTuple(args, "s", &name)) {
return NULL;
}
return PyUnicode_FromFormat("Hello, %s!", name);
}

// 方法表:定义模块中的函数
static PyMethodDef MyMethods[] = {
{"hello", hello, METH_VARARGS, "Say hello to someone."},
{NULL, NULL, 0, NULL} // 哨兵:方法表结束标记
};

// 模块定义
static struct PyModuleDef mymodule = {
PyModuleDef_HEAD_INIT,
"mymodule",
"My first C extension module.",
-1, // 模块状态(-1 表示全局状态)
MyMethods
};

// 模块初始化函数:必须是 PyInit_模块名()
PyMODINIT_FUNC PyInit_mymodule(void) {
return PyModule_Create(&mymodule);
}

编译安装

1
2
3
4
5
6
7
8
# setup.py
from setuptools import setup, Extension

setup(
name='mymodule',
version='1.0',
ext_modules=[Extension('mymodule', ['mymodule.c'])],
)
1
2
3
4
# 编译并安装
python setup.py build_ext --inplace
# 或安装到系统
pip install .

使用

1
2
import mymodule
print(mymodule.hello("Python")) # Hello, Python!

📊 参数解析

PyArg_ParseTuple 是 C 扩展中最重要的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static PyObject* math_ops(PyObject* self, PyObject* args) {
int a, b;
char* op;

// "is" 表示:int 和 string
if (!PyArg_ParseTuple(args, "is", &a, &op)) {
return NULL;
}

if (strcmp(op, "add") == 0) {
return PyLong_FromLong(a + b);
}
return PyLong_FromLong(a - b);
}

常用格式字符串

格式Python 类型C 类型
iintint
lintlong
dfloatdouble
sstrchar*
O任意PyObject*
iiint, intint, int
(i,i)tupleint*, int*

🧠 处理 Python 对象

操作 Python 列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static PyObject* sum_list(PyObject* self, PyObject* args) {
PyObject* py_list;
// "O" 解析任意 Python 对象
if (!PyArg_ParseTuple(args, "O", &py_list)) {
return NULL;
}

if (!PyList_Check(py_list)) {
PyErr_SetString(PyExc_TypeError, "Expected a list!");
return NULL;
}

double total = 0;
Py_ssize_t size = PyList_Size(py_list);

for (Py_ssize_t i = 0; i < size; i++) {
PyObject* item = PyList_GetItem(py_list, i);
// PyNumber_Check 可以检查是否是数字
if (PyLong_Check(item)) {
total += PyLong_AsDouble(item);
}
}

return PyFloat_FromDouble(total);
}

引用计数(最重要!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 规则 1:PyArg_ParseTuple 不增加引用计数,无需 DECREF
PyObject* obj;
PyArg_ParseTuple(args, "O", &obj);
// obj 借用引用,不要 Py_DECREF(obj)

// 规则 2:PyList_GetItem 返回借用引用,不要 DECREF
PyObject* item = PyList_GetItem(list, 0); // 借用
// 不要 Py_DECREF(item)

// 规则 3:PyLong_FromLong 返回新引用,必须 DECREF
PyObject* result = PyLong_FromLong(42);
return result; // 转移所有权给调用者
// 或者 Py_DECREF(result);

// 规则 4:Py_INCREF 增加引用计数
Py_INCREF(obj); // 增加引用
Py_DECREF(obj); // 减少引用

引用计数错误是 C 扩展中最常见的 bug

  • 忘记 DECREF → 内存泄漏
  • 多次 DECREF → 崩溃

⚡ 高性能示例:向量化加法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// fastmath.c
static PyObject* vector_add(PyObject* self, PyObject* args) {
PyObject *list_a, *list_b;

if (!PyArg_ParseTuple(args, "OO", &list_a, &list_b)) {
return NULL;
}

Py_ssize_t size = PyList_Size(list_a);
PyObject* result = PyList_New(size);

for (Py_ssize_t i = 0; i < size; i++) {
double a = PyFloat_AsDouble(PyList_GetItem(list_a, i));
double b = PyFloat_AsDouble(PyList_GetItem(list_b, i));
PyList_SET_ITEM(result, i, PyFloat_FromDouble(a + b));
}

return result;
}

对比性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Python 版本
def py_add(a, b):
return [x + y for x, y in zip(a, b)]

# C 扩展版本
import fastmath

import time
import random
n = 10**6
a = [random.random() for _ in range(n)]
b = [random.random() for _ in range(n)]

t1 = time.perf_counter()
c1 = py_add(a, b)
print(f'Python: {time.perf_counter()-t1:.3f}s')

t2 = time.perf_counter()
c2 = fastmath.vector_add(a, b)
print(f'C 扩展: {time.perf_counter()-t2:.3f}s')

🔄 Cython(更简单的方式)

如果不想直接写 C 代码,可以用 Cython:

1
2
3
4
5
6
7
8
# fastcython.pyx
def vector_add(list a, list b):
cdef int i
cdef int n = len(a)
cdef list result = [0.0] * n
for i in range(n):
result[i] = float(a[i]) + float(b[i])
return result
1
2
3
4
5
6
7
8
# setup.py
from setuptools import setup
from Cython.Build import cythonize

setup(
name='fastcython',
ext_modules=cythonize('fastcython.pyx'),
)

💡 本节作业

  1. 用 C 扩展实现一个 factorial(n) 函数
  2. PyArg_ParseTuple 解析多个参数
  3. 尝试用 Cython 改写一个 Python 函数

🎯 本讲总结

C 扩展基础:PyObject*、PyArg_ParseTuple、方法表、模块初始化函数。

参数解析:格式字符串(i、s、O 等)解析 Python 参数为 C 类型。

引用计数:借用引用 vs 新引用,DECREF 错误是最常见的 bug。

性能对比:C 扩展比纯 Python 快 10-100 倍,Cython 是更简单的替代方案。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》


🔗 课程导航

上一讲:模块导入系统 | 下一讲:性能分析与优化


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!