首页
/ CPython 迭代器协议 C API 详解:PyIter_Check、PyIter_NextItem 与 PyIter_Send 实战指南

CPython 迭代器协议 C API 详解:PyIter_Check、PyIter_NextItem 与 PyIter_Send 实战指南

2026-09-04 19:26:43作者:尤峻淳Whitney

本文基于 CPython 官方文档 Doc/c-api/iter.rst("Iterator Protocol" 章节)展开,系统讲解 CPython C API 中专门用于操作迭代器的五个接口:PyIter_CheckPyAIter_CheckPyIter_NextItemPyIter_NextPyIter_Send。读完后你将能够:在 C 扩展中安全地判断对象是否为迭代器/异步迭代器、以不丢失异常的方式逐项拉取迭代器内容、向生成器发送值并正确解读 PySendResult 的三种状态,并且能从 Objects/abstract.c 源码层面理解这些接口的内部实现与错误处理语义。

1. 迭代器协议 C API 全景

CPython 将迭代器协议(__iter__ / __next__)的 C 层封装集中在一组函数里,对应头文件为 Include/abstract.h 中的 "Iterators" 区块。该文档列出的核心接口如下:

接口 作用 最低版本
PyIter_Check(PyObject *o) 判断对象是否为迭代器 始终可用
PyAIter_Check(PyObject *o) 判断对象是否实现 AsyncIterator 协议 3.10
PyIter_NextItem(PyObject *iter, PyObject **item) 取下一个值,三态返回(成功/耗尽/错误) 3.14
PyIter_Next(PyObject *o) PyIter_NextItem 的旧版兼容实现 始终可用
PyIter_Send(PyObject *iter, PyObject *arg, PyObject **presult) 向生成器/迭代器发送值 3.10

一个典型的 C 扩展迭代场景是"获取迭代器 → 检查合法性 → 循环拉取 → 处理异常":

PyObject *iter = PyObject_GetIter(obj);   /* 等价于 Python 的 iter(obj) */
if (iter == NULL) {
    return NULL;                          /* iter() 失败,异常已设置 */
}

if (!PyIter_Check(iter)) {
    /* PyObject_GetIter 保证返回迭代器;手动构造迭代器时需先检查 */
    Py_DECREF(iter);
    return NULL;
}

PyObject *item;
int rc;
while ((rc = PyIter_NextItem(iter, &item)) > 0) {
    /* rc == 1:item 是迭代器的下一个值(强引用),处理完后必须释放 */
    ...
    Py_DECREF(item);
}
Py_DECREF(iter);

if (rc == -1) {
    return NULL;                          /* 迭代过程抛出异常 */
}
/* rc == 0:正常耗尽,无异常 */

PyObject_GetIter / PyObject_GetAIter 的声明见 Include/abstract.h,它们分别对应 Python 层的 iter(obj)aiter(obj)。从 Objects/abstract.c 的实现可以看到,PyObject_GetIter 在调用 __iter__ 后会用 PyIter_Check 校验返回值——如果 __iter__ 返回的不是迭代器,会抛出 __iter__() must return an iterator, not ...TypeError。这正是 PyIter_Check 在解释器内部的真实用法。

2. PyIter_Check:迭代器的"类型安全"检查

文档定义:

int PyIter_Check(PyObject *o):如果对象 o 可以被安全地传给 PyIter_NextItem,返回非零值,否则返回 0。此函数总是成功。

2.1 源码实现:看 tp_iternext 槽

Objects/abstract.c 中,PyIter_Check 的实现只检查类型槽:

int
PyIter_Check(PyObject *obj)
{
    PyTypeObject *tp = Py_TYPE(obj);
    return (tp->tp_iternext != NULL &&
            tp->tp_iternext != &_PyObject_NextNotImplemented);
}

可以推断出两个要点:

  1. 判断的是类型槽,不是对象状态。只要类型定义了 tp_iternext(且不是占位函数 _PyObject_NextNotImplemented),该类型的任何实例都会通过检查。
  2. 它"总是成功"——不设置异常、不消耗引用,因此可以放心地在任何分支中调用。

2.2 PyAIter_Check:异步迭代器的对应检查

文档中 PyAIter_Check(3.10 加入)与上面互为镜像,用于判断对象是否提供 AsyncIterator 协议。其源码见 Objects/abstract.c

int
PyAIter_Check(PyObject *obj)
{
    PyTypeObject *tp = Py_TYPE(obj);
    return (tp->tp_as_async != NULL &&
            tp->tp_as_async->am_anext != NULL &&
            tp->tp_as_async->am_anext != &_PyObject_NextNotImplemented);
}

即检查 tp_as_async->am_anext 槽。同样的模式也出现在 PyObject_GetAIter 的返回值校验中(Objects/abstract.c):__aiter__() 若返回非异步迭代器,会抛出 TypeError: ...__aiter__() must return an async iterator, not ...

3. PyIter_NextItem:3.14 推荐的"取下一项"方式

3.1 文档语义:三态返回值

PyIter_NextItem 是 3.14 新增接口(见 Misc/NEWS.d/3.14.0a1.rst 的变更说明:"Add PyIter_NextItem to replace PyIter_Next, which has an ambiguous return value"),其返回值语义为:

  • 返回 1*item 被设置为迭代器下一个值的强引用(成功取到一项);
  • 返回 0*item 被设置为 NULL(迭代器已无剩余值,正常耗尽);
  • 返回 -1*item 被设置为 NULL,并设置异常(错误)。

头文件声明见 Include/abstract.h,注意其 Py_LIMITED_API 门槛为 0x030e0000(即受限 API 3.14 起才可用)。

3.2 为什么需要它:与 PyIter_Next 的歧义对比

旧接口 PyIter_Next 的返回约定是(见 Objects/abstract.c 注释):

  • 出错:返回 NULLPyErr_Occurred() 为真;
  • 正常耗尽:返回 NULL清除 StopIterationPyErr_Occurred() 为假;
  • 成功:返回下一项。

"NULL + 无异常 = 耗尽,NULL + 有异常 = 错误" 的双通道约定迫使调用方在每次 NULL 返回后手动调用 PyErr_Occurred() 区分情形,且"耗尽"与"错误"都靠同一个 NULL 指针表达,容易写错。PyIter_NextItem 把三种状态显式编码进整型返回值,消除了歧义;文档明确指出 PyIter_Next 仅是"为向后兼容而保留的旧版本,应优先使用 PyIter_NextItem"。

3.3 内部实现:StopIteration 的归一化处理

两者的核心都是 Objects/abstract.c 中的静态函数 iternext

static int
iternext(PyObject *iter, PyObject **item)
{
    iternextfunc tp_iternext = Py_TYPE(iter)->tp_iternext;
    if ((*item = tp_iternext(iter))) {
        return 1;
    }

    PyThreadState *tstate = _PyThreadState_GET();
    /* When the iterator is exhausted it must return NULL;
     * a StopIteration exception may or may not be set. */
    if (!_PyErr_Occurred(tstate)) {
        return 0;
    }
    if (_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
        _PyErr_Clear(tstate);
        return 0;
    }

    /* Error case: an exception (different than StopIteration) is set. */
    return -1;
}

这段实现揭示了 CPython 迭代器协议的一个关键约定:迭代器耗尽时返回 NULL,且可能伴随一个 StopIteration 异常。协议层面,__next__ 抛出的 StopIteration 是迭代终止的正常信号;iternext 会把它清除并归一化为"耗尽"(返回 0),而其它任何异常都原样保留并返回 -1。因此 C 扩展开发者无需自己处理 StopIteration 的匹配与清除——两个 PyIter_Next* 接口都已替你完成。

PyIter_NextItem 与旧接口还有一处行为差异(Objects/abstract.c):

int
PyIter_NextItem(PyObject *iter, PyObject **item)
{
    assert(iter != NULL);
    assert(item != NULL);

    if (Py_TYPE(iter)->tp_iternext == NULL) {
        *item = NULL;
        PyErr_Format(PyExc_TypeError, "expected an iterator, got '%T'", iter);
        return -1;
    }

    return iternext(iter, item);
}

传入非迭代器对象时,PyIter_NextItem优雅地抛出 TypeError: expected an iterator, got '...';而 PyIter_Next 直接调用 tp_iternext 槽,传入非迭代器会解引用 NULL 指针导致崩溃。测试 Lib/test/test_capi/test_abstract.py 精确固化了这一差异:

def test_iter_next(self):
    from _testcapi import PyIter_Next
    self.run_iter_api_test(PyIter_Next)
    # CRASHES PyIter_Next(10)

def test_iter_nextitem(self):
    from _testcapi import PyIter_NextItem
    self.run_iter_api_test(PyIter_NextItem)
    regex = "expected.*iterator.*got.*'int'"
    with self.assertRaisesRegex(TypeError, regex):
        PyIter_NextItem(10)

C 端的暴露实现位于 Modules/_testcapi/abstract.c

3.4 官方测试用例覆盖的三种路径

同一测试文件中的 run_iter_api_testLib/test/test_capi/test_abstract.py)用同一套数据验证了三种路径:

  1. 成功路径:对空元组、空列表、(1, 2, 3)[1, 2, 3]、字符串 "123" 逐一迭代,直到返回 None/耗尽,断言收集到的序列等于 list(data)
  2. 错误路径Broken 类的前三次 __next__ 返回 1/2/3,第四次抛出 TypeError('bad type'),测试断言第 4 次调用透传了该异常。

这组用例是验证你的 C 扩展是否正确使用 PyIter_NextItem 的好参照:任何"耗尽即 NULL"的错误假设(例如把耗尽当错误)都会在第 1 条用例的空容器上暴露。

4. PyIter_Send 与 PySendResult:生成器双向通信

4.1 结果枚举定义

文档定义了枚举类型 PySendResult(3.10 加入),用于表示 PyIter_Send 的不同结果。其定义位于 Include/object.h

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
/* Result of calling PyIter_Send */
typedef enum {
    PYGEN_RETURN = 0,
    PYGEN_ERROR = -1,
    PYGEN_NEXT = 1
} PySendResult;
#endif

4.2 接口语义

PySendResult PyIter_Send(PyObject *iter, PyObject *arg, PyObject **presult) 的文档约定:

  • PYGEN_RETURN:迭代器返回(生成器正常结束)。返回值通过 *presult 传出(即生成器的 return 值);
  • PYGEN_NEXT:迭代器产出yield)。产出的值通过 *presult 传出;
  • PYGEN_ERROR:迭代器抛出异常。此时 *presult 被设置为 NULL,异常留在解释器中。

注意 arg 不能为 NULL——实现中有 assert(arg != NULL),且约定以 Py_None 表示"不发送值",此时行为退化为普通的 next()

4.3 源码实现:优先走 am_send 槽

Objects/abstract.c 的实现展示了 PyIter_Send 的两条路径:

PySendResult
PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result)
{
    assert(arg != NULL);
    assert(result != NULL);
    if (Py_TYPE(iter)->tp_as_async && Py_TYPE(iter)->tp_as_async->am_send) {
        PySendResult res = Py_TYPE(iter)->tp_as_async->am_send(iter, arg, result);
        assert(_Py_CheckSlotResult(iter, "am_send", res != PYGEN_ERROR));
        return res;
    }
    if (arg == Py_None && PyIter_Check(iter)) {
        *result = Py_TYPE(iter)->tp_iternext(iter);
    }
    else {
        *result = PyObject_CallMethodOneArg(iter, &_Py_ID(send), arg);
    }
    if (*result != NULL) {
        return PYGEN_NEXT;
    }
    if (_PyGen_FetchStopIterationValue(result) == 0) {
        return PYGEN_RETURN;
    }
    return PYGEN_ERROR;
}

从源码结构看,其分派逻辑是:

  1. 异步生成器:类型实现了 tp_as_async->am_send 槽时直接调用(生成器的 send 槽路径);
  2. 同步迭代器 + 无参argPy_None 且对象通过 PyIter_Check 时,直接调用 tp_iternext,省掉一次方法调用开销;
  3. 普通对象:回退到 PyObject_CallMethodOneArg(iter, "send", arg),等价于 Python 层的 iter.send(arg)

返回值的归一化在函数尾部:调用成功得到非 NULL 结果是 PYGEN_NEXT;若 __next__/send 抛出的 StopIteration 能被 _PyGen_FetchStopIterationValue 捕获(即生成器携带 return 值结束),结果为 PYGEN_RETURN 且生成器的返回值放入 *result;其余任何异常都是 PYGEN_ERROR。解释器内部还有一个包装函数 _PyIter_SendObjects/abstract.c),用 PySendResultPair(定义于 Include/internal/pycore_abstract.h)把"结果枚举 + 值对象"打包返回,供字节码求值器使用。

4.4 一个完整的生成器消费示例

/* 消费一个 Python 生成器,模拟 Python 层 next() 与 send() 的混用 */
PyObject *gen = /* 已获得的生成器对象 */;
PyObject *result = NULL;
PyObject *none = Py_None;

PySendResult rc = PyIter_Send(gen, none, &result);   /* 等价于 next(gen) */
while (rc == PYGEN_NEXT) {
    /* result 是 yield 出的值(强引用) */
    Py_DECREF(result);
    Py_SETREF(result, NULL);
    PyObject *value = /* 要 send 的值,无值则传 Py_None */;
    rc = PyIter_Send(gen, value, &result);
}

if (rc == PYGEN_RETURN) {
    /* result 是生成器 return 的值,用完 Py_DECREF(result) */
}
else if (rc == PYGEN_ERROR) {
    /* 异常已设置:处理或向上抛出;result 为 NULL */
}

5. 版本可用性总结与最佳实践

接口 Python 版本要求 受限 API(Py_LIMITED_API
PyIter_Check 始终 可用
PyAIter_Check ≥ 3.10 0x030A0000(3.10)
PyIter_Next 始终 可用
PyIter_NextItem ≥ 3.14 0x030e0000(3.14)
PyIter_Send / PySendResult ≥ 3.10 0x030A0000(3.10)

版本门槛的依据来自 Include/abstract.hInclude/object.h 中的 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= ... 编译条件。

基于源码与文档,使用这些接口时的最佳实践:

  1. 优先 PyIter_NextItem(3.14+):三态整型返回值消除了"NULL 到底意味着耗尽还是出错"的歧义,且对非迭代器输入会抛 TypeError 而非崩溃;
  2. 检查永远免费PyIter_Check / PyAIter_Check 总是成功、不设置异常,可以在热路径中放心调用,用于区分"正常耗尽"与"非法输入";
  3. 管理好强引用PyIter_NextItem 成功时的 *itemPyIter_Send 非错误时的 *result 都是调用方接管的强引用,必须 Py_DECREF
  4. 不要手动清理 StopIterationiternext 已按协议完成 StopIteration 的匹配与清除(Objects/abstract.c),你只需处理"非 StopIteration 异常"这一种错误情形;
  5. PyIter_Send 中用 Py_None 代替 NULL:发送"空值"时传 Py_None,它会让实现走更高效的 tp_iternext 直接调用路径。

相关深入阅读:迭代器对象本身(iter() 的返回类型)由 Objects/iterobject.c 实现;生成器与 StopIteration 值语义见 Objects/genobject.c;受限 ABI 导出清单中这些接口的收录情况可查 Misc/stable_abi.tomlDoc/data/stable_abi.dat

登录后查看全文
热门项目推荐
相关项目推荐