0Pricing
Python Academy · 课时

编写 Python C 扩展模块

使用 Python/C API 构建一个简单的 .so 扩展。

编写 Python C 扩展模块 是 CoddyKit 上的免费 Python Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。

C 扩展的构成

一个最小的 C 扩展包含:方法函数、方法表、模块定义结构体,以及 PyInit_ 入口点。

// myext.c skeleton
#include <Python.h>

static PyObject* say_hello(PyObject* self, PyObject* args) {
    Py_RETURN_NONE;
}

static PyMethodDef methods[] = {
    {"say_hello", say_hello, METH_NOARGS, "Print hello"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT, "myext", NULL, -1, methods
};

PyMODINIT_FUNC PyInit_myext(void) {
    return PyModule_Create(&module);
}

解析参数

PyArg_ParseTuple(args, "ii", &a, &b) 会将 Python 参数解析为 C 变量。格式代码:i=int,d=double,s=char*,O=PyObject*。

static PyObject* add(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL;
    return PyLong_FromLong(a + b);
}

返回值

请使用以下函数构造 Python 返回值:PyLong_FromLong、PyFloat_FromDouble、PyUnicode_FromString、Py_BuildValue。

// Return a Python tuple (int, float)
static PyObject* stats(PyObject* self, PyObject* args) {
    int n = 10;
    double avg = 5.0;
    return Py_BuildValue("(id)", n, avg);
    // Py_BuildValue format: i=int d=double s=str
}

引用计数

每个 PyObject* 都有一个引用计数。请谨慎使用 Py_INCREF/Py_DECREF。C 函数返回的返回值会将所有权交给调用方(引用被移交)。

static PyObject* make_list(PyObject* self, PyObject* args) {
    PyObject* lst = PyList_New(3);
    for (int i = 0; i < 3; i++) {
        // PyList_SET_ITEM steals the reference:
        PyList_SET_ITEM(lst, i, PyLong_FromLong(i));
    }
    return lst;   // caller owns the list
}

引发异常

请使用 PyErr_SetString(PyExc_ValueError, "msg") 从 C 引发 Python 异常,然后返回 NULL。

static PyObject* safe_div(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) return NULL;
    if (b == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
        return NULL;
    }
    return PyLong_FromLong(a / b);
}

用于 C 扩展的 setup.py

请使用 setuptools.Extension 声明 C 源文件。请使用 python setup.py build_ext --inplace 进行构建。

# setup.py
from setuptools import setup, Extension

setup(
    name="myext",
    ext_modules=[
        Extension(
            "myext",
            sources=["myext.c"],
            extra_compile_args=["-O2"],
        )
    ]
)
# python setup.py build_ext --inplace
# import myext; myext.add(3, 4)

使用已构建的扩展

构建完成后,请像导入任何 Python 模块一样导入该扩展。Python 会查找 myext.so(Windows 上则查找 .pyd)来定位它。

# After: python setup.py build_ext --inplace
import myext
print(myext.add(3, 4))        # 7
print(myext.safe_div(10, 2))  # 5
# myext.safe_div(1, 0)         # ZeroDivisionError

关键字参数

请将 PyArg_ParseTupleAndKeywords 与关键字列表结合使用,以支持 C 函数中的关键字参数。

static char* kwargs[] = {"x", "y", NULL};

static PyObject* hypot_c(PyObject* self,
                          PyObject* args,
                          PyObject* kw) {
    double x, y;
    if (!PyArg_ParseTupleAndKeywords(args, kw, "dd",
                                     kwargs, &x, &y))
        return NULL;
    return PyFloat_FromDouble(sqrt(x*x + y*y));
}

模块级常量

请在 PyInit_ 中使用 PyModule_AddIntConstant 向模块添加整数或字符串常量。

PyMODINIT_FUNC PyInit_myext(void) {
    PyObject* m = PyModule_Create(&module);
    if (!m) return NULL;
    PyModule_AddIntConstant(m, "VERSION", 1);
    PyModule_AddStringConstant(m, "AUTHOR", "Alice");
    return m;
}

释放 GIL

请使用 Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS 包裹纯 C 工作,使其他 Python 线程能够在 C 代码执行期间运行。

static PyObject* heavy(PyObject* self, PyObject* args) {
    long n;
    if (!PyArg_ParseTuple(args, "l", &n)) return NULL;
    long result;
    Py_BEGIN_ALLOW_THREADS
        result = slow_c_computation(n);  // GIL released
    Py_END_ALLOW_THREADS
    return PyLong_FromLong(result);
}

测试扩展

请通过 Python 接口使用 pytest 测试 C 扩展。无需专门的 C 测试工具。

# test_myext.py
import pytest
import myext

def test_add(): assert myext.add(3, 4) == 7
def test_zero_div():
    with pytest.raises(ZeroDivisionError):
        myext.safe_div(1, 0)

快速检查

如果 Python 异常已经设置,C 扩展函数必须返回什么来表示这一点?

总结

C 扩展需要:方法函数(解析参数并返回 PyObject*)、方法表、模块定义结构体,以及 PyInit_name()。请使用 PyErr_SetString 设置异常,并返回 NULL 来引发异常。对于纯 C 工作,请释放 GIL。

常见问题解答

「编写 Python C 扩展模块」课时是免费的吗?

是的 — 「编写 Python C 扩展模块」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。

「编写 Python C 扩展模块」这节课中我会学到什么?

使用 Python/C API 构建一个简单的 .so 扩展。 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Python Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「编写 Python C 扩展模块」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Python Academy 课中编写并运行代码吗?

能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为何使用 C 扩展?使用场景与权衡
  2. ctypes:从 Python 调用 C 库
  3. cffi:C 外部函数接口
  4. 编写 Python C 扩展模块
← 返回 Python Academy