
CPython 迭代器协议 C API 详解PyIter_Check、PyIter_NextItem 与 PyIter_Send 实战指南【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文基于 CPython 官方文档 Doc/c-api/iter.rstIterator 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.10PyIter_NextItem(PyObject *iter, PyObject **item)取下一个值三态返回成功/耗尽/错误3.14PyIter_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 1item 是迭代器的下一个值强引用处理完后必须释放 */ ... 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_Check3.10 加入与上面互为镜像用于判断对象是否提供AsyncIterator协议。其源码见 Objects/abstract.cint 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_NextItem3.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且清除StopIterationPyErr_Occurred()为假成功返回下一项。NULL 无异常 耗尽NULL 有异常 错误 的双通道约定迫使调用方在每次NULL返回后手动调用PyErr_Occurred()区分情形且耗尽与错误都靠同一个NULL指针表达容易写错。PyIter_NextItem把三种状态显式编码进整型返回值消除了歧义文档明确指出PyIter_Next仅是为向后兼容而保留的旧版本应优先使用PyIter_NextItem。3.3 内部实现StopIteration 的归一化处理两者的核心都是 Objects/abstract.c 中的静态函数iternextstatic 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.cint 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, 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 结果枚举定义文档定义了枚举类型PySendResult3.10 加入用于表示PyIter_Send的不同结果。其定义位于 Include/object.h#if !defined(Py_LIMITED_API) || Py_LIMITED_API0 0x030A0000 /* Result of calling PyIter_Send */ typedef enum { PYGEN_RETURN 0, PYGEN_ERROR -1, PYGEN_NEXT 1 } PySendResult; #endif4.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_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 版本要求受限 APIPy_LIMITED_APIPyIter_Check始终可用PyAIter_Check≥ 3.10≥0x030A00003.10PyIter_Next始终可用PyIter_NextItem≥ 3.14≥0x030e00003.14PyIter_Send/PySendResult≥ 3.10≥0x030A00003.10版本门槛的依据来自 Include/abstract.h 与 Include/object.h 中的#if !defined(Py_LIMITED_API) || Py_LIMITED_API0 ...编译条件。基于源码与文档使用这些接口时的最佳实践优先PyIter_NextItem3.14三态整型返回值消除了NULL 到底意味着耗尽还是出错的歧义且对非迭代器输入会抛TypeError而非崩溃检查永远免费PyIter_Check/PyAIter_Check总是成功、不设置异常可以在热路径中放心调用用于区分正常耗尽与非法输入管理好强引用PyIter_NextItem成功时的*item与PyIter_Send非错误时的*result都是调用方接管的强引用必须Py_DECREF不要手动清理StopIterationiternext已按协议完成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。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考