咨询基于CPython API的C语言生成器函数底层原理与实现方法
我完全懂你的困惑——CPython API文档在生成器这块确实有点绕,尤其是第一次接触的时候。先给你拆解清楚底层逻辑,再一步步实现你要的那个生成器函数。
生成器的CPython底层工作原理
Python里的生成器本质上是个状态机,和普通函数的核心区别在于:
- 普通函数执行到
return就彻底结束,所有局部状态直接销毁; - 生成器执行到
yield时会暂停执行,把当前的栈帧、局部变量、程序计数器等状态全部保存下来,然后把yield的值返回给调用者; - 当你调用
next()或者send()时,生成器会从暂停的位置无缝恢复执行,直到遇到下一个yield或者走到函数末尾抛出StopIteration。
在CPython内部,生成器对应PyGenObject结构体,它封装了所有需要保存的状态数据。我们要做的就是手动创建这个对象,定义它的执行逻辑,以及处理暂停/恢复的核心流程。
实现等效于
def gen_func(*args): for arg in args: yield arg的C生成器 先给你列几个核心的CPython API函数,你得先熟悉它们:
PyGen_NewWithQualName():创建一个新的生成器对象;Py_YIELD_VALUE():暂停生成器并返回指定值,下次恢复时从这个位置继续;PyObject_GetIter():创建对象的迭代器;Py_INCREF/Py_DECREF:管理对象引用计数,必须严格处理,否则会内存泄漏或者崩溃。
下面是完整的可运行代码,我加了详细注释:
#include <Python.h> // 生成器的核心迭代逻辑:每次调用next()时会执行这个函数 static PyObject* gen_func_iter(PyObject *self) { // 把self转换成生成器对象指针 PyGenObject *gen = (PyGenObject*)self; // 获取生成器的栈帧,里面存着我们传入的参数 PyFrameObject *frame = gen->gi_frame; // 从栈帧的局部变量里取出*args参数(我们在创建生成器时放进去的) PyObject *args = PyDict_GetItemString(PyFrame_GetLocals(frame), "args"); if (!args) { PyErr_SetString(PyExc_RuntimeError, "Failed to get args from generator"); return NULL; } // 给args创建迭代器,准备遍历 PyObject *iter = PyObject_GetIter(args); if (!iter) { return NULL; // 出错时返回NULL,CPython会自动处理异常 } PyObject *item; // 遍历迭代器,逐个yield元素 while ((item = PyIter_Next(iter))) { // 暂停生成器,把item返回给调用者;下次恢复时会从这里继续执行 // Py_YIELD_VALUE会处理状态保存和恢复的逻辑 if (Py_YIELD_VALUE(item) != NULL) { // 如果恢复时出现异常,清理资源后返回NULL Py_DECREF(item); Py_DECREF(iter); return NULL; } // 释放item的引用,因为已经通过yield返回给调用者了 Py_DECREF(item); } // 遍历结束,释放迭代器的引用 Py_DECREF(iter); // 抛出StopIteration,标记生成器终止 PyErr_SetNone(PyExc_StopIteration); return NULL; } // 定义我们的生成器类型 static PyTypeObject GenFuncType = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "c_gen.gen_func", // 类型的全名 .tp_basicsize = sizeof(PyGenObject), // 生成器对象的大小 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, // 类型标记 .tp_iter = PyObject_SelfIter, // 生成器的迭代器就是它自己 .tp_iternext = gen_func_iter, // 迭代时调用的核心函数 }; // 对外暴露的Python函数:用来创建我们的生成器(相当于调用gen_func(*args)) static PyObject* create_gen_func(PyObject *self, PyObject *args, PyObject *kwargs) { // 我们只接受位置参数,对应Python里的*args // 先把传入的参数打包成一个元组,存到生成器的局部变量里 PyObject *gen_args = PyTuple_New(PyTuple_Size(args)); if (!gen_args) { return NULL; } for (Py_ssize_t i = 0; i < PyTuple_Size(args); i++) { PyObject *item = PyTuple_GetItem(args, i); Py_INCREF(item); // 增加引用计数,避免被提前销毁 PyTuple_SetItem(gen_args, i, item); } // 创建生成器对象 PyGenObject *gen = (PyGenObject*)PyGen_NewWithQualName( &GenFuncType, // 我们定义的生成器类型 gen_args, // 生成器的参数 NULL, // 代码对象(我们自己实现了迭代逻辑,所以传NULL) "__main__", // 生成器的模块名 "gen_func" // 生成器的函数名 ); Py_DECREF(gen_args); // 已经把args传给生成器了,释放我们的引用 if (!gen) { return NULL; } // 把args存到生成器的局部变量里,方便迭代函数获取 PyObject *locals = PyFrame_GetLocals(gen->gi_frame); if (PyDict_SetItemString(locals, "args", gen_args) < 0) { Py_DECREF(gen); return NULL; } return (PyObject*)gen; } // 模块的方法列表 static PyMethodDef GenModuleMethods[] = { { "gen_func", (PyCFunction)create_gen_func, METH_VARARGS | METH_KEYWORDS, "Generator that yields each argument in sequence" }, {NULL, NULL, 0, NULL} // 结束标记 }; // 模块定义 static struct PyModuleDef genmodule = { PyModuleDef_HEAD_INIT, "c_gen", // 模块名 "C-implemented generator module", // 模块文档 -1, // 模块状态大小(-1表示不需要状态) GenModuleMethods }; // 模块初始化函数(必须叫PyInit_<模块名>) PyMODINIT_FUNC PyInit_c_gen(void) { // 初始化我们的生成器类型 if (PyType_Ready(&GenFuncType) < 0) { return NULL; } // 创建模块对象 PyObject *m = PyModule_Create(&genmodule); if (!m) { return NULL; } // 把生成器类型添加到模块里(可选,方便调试) Py_INCREF(&GenFuncType); if (PyModule_AddObject(m, "gen_func_type", (PyObject*)&GenFuncType) < 0) { Py_DECREF(&GenFuncType); Py_DECREF(m); return NULL; } return m; }
测试方法
把上面的代码保存为c_gen.c,然后写一个setup.py来编译成扩展模块:
from setuptools import setup, Extension module = Extension('c_gen', sources=['c_gen.c']) setup( name='c_gen', version='1.0', ext_modules=[module] )
执行python setup.py build_ext --inplace编译,之后就可以在Python里测试了:
import c_gen gen = c_gen.gen_func(1, "hello", 3.14) print(next(gen)) # 输出 1 print(next(gen)) # 输出 hello print(next(gen)) # 输出 3.14 next(gen) # 抛出 StopIteration
关键注意事项
- 引用计数必须严格管理:每个
Py_INCREF都要有对应的Py_DECREF,否则会导致内存泄漏或者CPython崩溃; - 生成器状态的依赖:我们的迭代逻辑完全依赖
PyGenObject的栈帧保存状态,不要随便修改栈帧的其他内容; - 异常处理:CPython API函数出错时会返回
NULL并设置异常,我们只需要把NULL往上抛,CPython会自动处理异常的传播。
内容的提问来源于stack exchange,提问作者mental
相关产品推荐
相关产品推荐

