You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:39:26