CPython 迭代器协议 C API 详解:PyIter_Check、PyIter_NextItem 与 PyIter_Send 实战指南
本文基于 CPython 官方文档 Doc/c-api/iter.rst("Iterator Protocol" 章节)展开,系统讲解 CPython C API 中专门用于操作迭代器的五个接口:PyIter_Check、PyAIter_Check、PyIter_NextItem、PyIter_Next 与 PyIter_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);
}
可以推断出两个要点:
- 判断的是类型槽,不是对象状态。只要类型定义了
tp_iternext(且不是占位函数_PyObject_NextNotImplemented),该类型的任何实例都会通过检查。 - 它"总是成功"——不设置异常、不消耗引用,因此可以放心地在任何分支中调用。
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 注释):
- 出错:返回
NULL且PyErr_Occurred()为真; - 正常耗尽:返回
NULL且清除StopIteration,PyErr_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_test(Lib/test/test_capi/test_abstract.py)用同一套数据验证了三种路径:
- 成功路径:对空元组、空列表、
(1, 2, 3)、[1, 2, 3]、字符串"123"逐一迭代,直到返回None/耗尽,断言收集到的序列等于list(data); - 错误路径:
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;
}
从源码结构看,其分派逻辑是:
- 异步生成器:类型实现了
tp_as_async->am_send槽时直接调用(生成器的send槽路径); - 同步迭代器 + 无参:
arg为Py_None且对象通过PyIter_Check时,直接调用tp_iternext,省掉一次方法调用开销; - 普通对象:回退到
PyObject_CallMethodOneArg(iter, "send", arg),等价于 Python 层的iter.send(arg)。
返回值的归一化在函数尾部:调用成功得到非 NULL 结果是 PYGEN_NEXT;若 __next__/send 抛出的 StopIteration 能被 _PyGen_FetchStopIterationValue 捕获(即生成器携带 return 值结束),结果为 PYGEN_RETURN 且生成器的返回值放入 *result;其余任何异常都是 PYGEN_ERROR。解释器内部还有一个包装函数 _PyIter_Send(Objects/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.h 与 Include/object.h 中的 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= ... 编译条件。
基于源码与文档,使用这些接口时的最佳实践:
- 优先
PyIter_NextItem(3.14+):三态整型返回值消除了"NULL 到底意味着耗尽还是出错"的歧义,且对非迭代器输入会抛TypeError而非崩溃; - 检查永远免费:
PyIter_Check/PyAIter_Check总是成功、不设置异常,可以在热路径中放心调用,用于区分"正常耗尽"与"非法输入"; - 管理好强引用:
PyIter_NextItem成功时的*item与PyIter_Send非错误时的*result都是调用方接管的强引用,必须Py_DECREF; - 不要手动清理
StopIteration:iternext已按协议完成StopIteration的匹配与清除(Objects/abstract.c),你只需处理"非 StopIteration 异常"这一种错误情形; PyIter_Send中用Py_None代替NULL:发送"空值"时传Py_None,它会让实现走更高效的tp_iternext直接调用路径。
相关深入阅读:迭代器对象本身(iter() 的返回类型)由 Objects/iterobject.c 实现;生成器与 StopIteration 值语义见 Objects/genobject.c;受限 ABI 导出清单中这些接口的收录情况可查 Misc/stable_abi.toml 与 Doc/data/stable_abi.dat。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00