CPython C API 迭代器对象详解:PySeqIter、PyCallIter 与全部内建迭代器类型
CPython 在 C 层面提供了两套通用迭代器对象(序列迭代器 PySeqIter_Type 与可调用+哨兵迭代器 PyCallIter_Type),以及一整套覆盖 enumerate、filter、map、reversed、zip、range、dict、list、set 等内建类型的迭代器类型对象。本文基于 CPython 仓库中 C API 迭代器文档,逐条解析这些 C API 的语义与边界,并结合 iterobject.c、bltinmodule.c 和 rangeobject.c 等源码,说明每个迭代器对象在解释器内部是如何被创建、推进和销毁的。读完后,你将能在 C 扩展中安全地构造与识别迭代器,理解 iter() 内建函数的两种形式在 C 层的实现路径,并知道何时应该改用 PyObject_GetIter 而不是直接构造具体迭代器类型。
两类通用迭代器对象总览
CPython 提供两个通用迭代器(general-purpose iterator objects),它们与 Python 内建函数 iter() 的两种调用形式一一对应:
- 序列迭代器(sequence iterator):作用于任何支持
__getitem__方法的序列对象,从下标 0 开始逐项取元素,直到序列对下标操作抛出IndexError为止; - 可调用+哨兵迭代器(callable and sentinel iterator):每次迭代调用一个无参可调用对象,将其返回值与哨兵值比较,当返回值等于哨兵时终止迭代。
这两个通用迭代器分别由 PySeqIter_New() 和 PyCallIter_New() 创建,对应的类型对象为 PySeqIter_Type 和 PyCallIter_Type,声明位于公共头文件 iterobject.h:
PyAPI_DATA(PyTypeObject) PySeqIter_Type;
PyAPI_DATA(PyTypeObject) PyCallIter_Type;
#define PySeqIter_Check(op) Py_IS_TYPE((op), &PySeqIter_Type)
PyAPI_FUNC(PyObject *) PySeqIter_New(PyObject *);
#define PyCallIter_Check(op) Py_IS_TYPE((op), &PyCallIter_Type)
PyAPI_FUNC(PyObject *) PyCallIter_New(PyObject *, PyObject *);
注意文档中的一处对应关系细节:PySeqIter_Type 是 iter 内建函数单参数形式(针对内建序列类型时)所返回迭代器的类型;PyCallIter_Type 则是 iter 双参数形式所返回迭代器的类型。
PySeqIter_Type:序列迭代器的构造与推进
PySeqIter_New 与 PySeqIter_Check
PySeqIter_Check(op) 判断 op 的类型是否精确是 PySeqIter_Type,文档明确说明该函数"always succeeds"(不会失败、不会设置错误)。从源码看,它被定义为宏 Py_IS_TYPE((op), &PySeqIter_Type)(见 iterobject.h#L11),即精确类型比较,不检查子类。
PySeqIter_New(PyObject *seq) 返回一个作用于通用序列对象 seq 的迭代器,当序列对下标操作抛出 IndexError 时迭代结束。其实现位于 iterobject.c#L17-L33:
PyObject *
PySeqIter_New(PyObject *seq)
{
seqiterobject *it;
if (!PySequence_Check(seq)) {
PyErr_BadInternalCall();
return NULL;
}
it = PyObject_GC_New(seqiterobject, &PySeqIter_Type);
if (it == NULL)
return NULL;
it->it_index = 0;
it->it_seq = Py_NewRef(seq);
_PyObject_GC_TRACK(it);
return (PyObject *)it;
}
可以注意到几个文档未展开但实际重要的行为:
- 参数必须是序列(
PySequence_Check),否则返回 NULL 并设置"bad internal call"错误——这是内部调用约定被违反时的典型错误; - 迭代器会持有序列的强引用(
Py_NewRef(seq)),生命周期由 GC 管理(Py_TPFLAGS_HAVE_GC)。
底层结构体定义在 iterobject.c#L11-L15:
typedef struct {
PyObject_HEAD
Py_ssize_t it_index;
PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} seqiterobject;
it_seq 在迭代器耗尽时被置为 NULL,这一注释对理解 pickling 行为很关键(见下文)。
iter_iternext:IndexError 与 StopIteration 双条件终止
推进逻辑 iter_iternext 位于 iterobject.c#L52-L83,核心流程:
- 若
it_seq == NULL(已耗尽),直接返回 NULL,不设置异常——这正是迭代器协议中"返回 NULL 表示StopIteration"的语义; - 若下标索引达到
PY_SSIZE_T_MAX,抛出OverflowError("iter index too large"); - 调用
PySequence_GetItem(seq, it->it_index)取元素,成功则索引自增 1; - 若取元素失败且异常为
IndexError或StopIteration,则清除异常、置空it_seq并释放序列引用,从而结束迭代。
第 4 点比文档描述更宽:除了文档提到的 IndexError,StopIteration 同样被视为序列终止信号,这是为了兼容那些在越界时抛出 StopIteration 的自定义序列。
__length_hint__、__reduce__ 与 __setstate__
序列迭代器还注册了三个方法(iterobject.c#L144-L149):
__length_hint__:若原序列支持len(),返回"剩余元素数"估计值(seqsize - it_index,下限 0);否则返回NotImplemented;__reduce__/__setstate__:支持 pickling。iter_reduce在迭代器未耗尽时返回(iter, it_seq, it_index),耗尽后返回(iter, ());iter_setstate在反序列化时以索引值恢复it_index(负索引被钳制为 0)。这解释了为什么 Python 层可以pickle.dumps(iter(range(10)))。
PySeqIter_Type 类型对象本身定义在 iterobject.c#L151-L182,其 tp_name 为 "iterator"——这正是你在 Python 层看到 repr(iter([1,2])) == '<list_iterator object at 0x...>' 之外的通用序列迭代器在 C 层的名字来源,且 tp_iter 使用 PyObject_SelfIter(迭代器迭代自己返回自身)。
PyCallIter_Type:可调用与哨兵值迭代器
PyCallIter_New 与 PyCallIter_Check
PyCallIter_Check(op) 与 PySeqIter_Check 类似,精确比较类型是否为 PyCallIter_Type,且永远成功;PyCallIter_New(callable, sentinel) 创建新的哨兵迭代器,其中 callable 是任何可以无参调用的 Python 可调用对象,每次调用应返回迭代的下一个元素;当返回值等于 sentinel 时终止迭代。
实现位于 iterobject.c#L186-L203:
typedef struct {
PyObject_HEAD
PyObject *it_callable; /* Set to NULL when iterator is exhausted */
PyObject *it_sentinel; /* Set to NULL when iterator is exhausted */
} calliterobject;
PyObject *
PyCallIter_New(PyObject *callable, PyObject *sentinel)
{
calliterobject *it;
it = PyObject_GC_New(calliterobject, &PyCallIter_Type);
if (it == NULL)
return NULL;
it->it_callable = Py_NewRef(callable);
it->it_sentinel = Py_NewRef(sentinel);
_PyObject_GC_TRACK(it);
return (PyObject *)it;
}
注意与 PySeqIter_New 的差异:构造函数不校验 callable 是否真的可调用——文档说"can be any Python callable object",但校验发生在 Python 层 iter(callable, sentinel) 的调用路径上(见下文"iter 内建函数的 C 实现"),C API 直接调用时由调用者自行保证。
calliter_iternext:等于哨兵或 StopIteration 均终止
推进函数 calliter_iternext 位于 iterobject.c#L223-L254,关键逻辑:
- 若
it_callable == NULL(已耗尽),直接返回 NULL; - 用
_PyObject_CallNoArgs(it->it_callable)调用可调用对象(无参调用的快速路径); - 用
PyObject_RichCompareBool(it_sentinel, result, Py_EQ)做相等性比较而非同一性比较——返回 0 表示"不相等",是最常见的快路径,直接返回该结果; - 比较结果为真(返回 1)或可调用抛出
StopIteration时,同时清空it_callable与it_sentinel,结束迭代; - 比较过程本身可能抛出异常(返回 -1),此时会走
Py_XDECREF(result)释放已取到的结果并让异常继续向上传播。
哨兵比较使用 == 语义而非 is,这意味着如果可调用返回的是与哨兵值相等但非同一对象的值(例如两个独立的 None 替代值或等值字符串),迭代同样终止。若哨兵本身被设计为单例(如 C 扩展里常见的"取不到数据返回哨兵对象"),这一语义差异通常不构成问题。
与序列迭代器一样,PyCallIter_Type 也实现了 __reduce__(iterobject.c#L256-L270):未耗尽时序列化为 (iter, callable, sentinel),耗尽后序列化为 (iter, ()),从而支持 pickle。
iter 内建函数的 C 实现路径
iter 内建函数把两种形式精确地映射到上述两个构造函数,实现位于 bltinmodule.c#L1898-L1914:
static PyObject *
builtin_iter(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *v;
if (!_PyArg_CheckPositional("iter", nargs, 1, 2))
return NULL;
v = args[0];
if (nargs == 1)
return PyObject_GetIter(v);
if (!PyCallable_Check(v)) {
PyErr_SetString(PyExc_TypeError,
"iter(v, w): v must be callable");
return NULL;
}
PyObject *sentinel = args[1];
return PyCallIter_New(v, sentinel);
}
从源码结构看可以确认三件事:
- 单参数形式并不直接调用
PySeqIter_New,而是先走PyObject_GetIter(v)——优先让对象自己提供__iter__,只有对象没有迭代协议时才回退到基于__getitem__的序列迭代; - 双参数形式先用
PyCallable_Check校验可调用性,再调用PyCallIter_New; PySeqIter_Type因此实际对应的是"iter()单参数形式、且对象走序列回退路径时针对内建序列类型"所返回的迭代器,与文档的措辞一致。
Range 对象:PyRange_Type 与 PyRange_Check
文档的 "Range Objects" 小节给出两个 API:
PyRange_Type:range对象的类型对象;PyRange_Check(PyObject *o):返回o是否为range实例,函数永远成功。
range 的迭代器选择是实现文档"Other Iterator Objects" 一节警告的典型实例。文档明确写道:"没有保证某个内建类型一定使用某个迭代器类型。例如,遍历 range 会根据 range 的大小使用两种迭代器类型之一,其他类型未来也可能开始使用类似方案,且不会提前警告。"
这段承诺在 rangeobject.c 中得到完整印证。range_iter(rangeobject.c#L1226-L1279)的逻辑是:
/* If all three fields and the length convert to long, use the int
* version */
lstart = PyLong_AsLong(r->start);
...
ulen = get_len_of_range(lstart, lstop, lstep);
if (ulen > (unsigned long)LONG_MAX) {
goto long_range;
}
...
return fast_range_iter(lstart, lstop, lstep, (long)ulen);
long_range:
it = PyObject_New(longrangeiterobject, &PyLongRangeIter_Type);
...
即当 start、stop、step 与长度都能装进 C long、且迭代过程无溢出风险时,使用基于 long 快速算术的 PyRangeIter_Type;否则回退到基于 PyObject*(任意精度 int)的 PyLongRangeIter_Type。两个类型对象分别定义在 rangeobject.c#L955 与 rangeobject.c#L1193,声明位于 rangeobject.h#L19-L20。这意味着 C 扩展代码中 Py_TYPE(it) == &PyRangeIter_Type 这类断言对大数值 range 会不成立——这正是文档发出警告的原因。
Builtin Iterator Types:五个无额外函数的类型对象
文档的 "Builtin Iterator Types" 小节说明:这些是包含在 C API 中、但不提供额外函数的内建迭代类型,列出它们只是为了完备性(for completeness):
| C 类型 | Python 类型 | 声明头文件 |
|---|---|---|
PyTypeObject PyEnum_Type |
enumerate |
Include/enumobject.h |
PyTypeObject PyFilter_Type |
filter |
Include/bltinmodule.h |
PyTypeObject PyMap_Type |
map |
Include/bltinmodule.h |
PyTypeObject PyReversed_Type |
reversed |
Include/enumobject.h |
PyTypeObject PyZip_Type |
zip |
Include/bltinmodule.h |
这五个类型对象允许 C 代码识别"这个迭代器是 enumerate/filter/map/reversed/zip 产生的",但仓库中不存在 PyEnum_New 之类的构造或推进函数——对它们而言,C API 只暴露"类型识别"能力,不提供构造或手动步进接口。
Other Iterator Objects:其余内建迭代器类型一览
文档最后列出 14 个各类内建对象迭代器的类型对象:
PyByteArrayIter_Type(bytearrayobject.h)、PyBytesIter_Type(bytesobject.h)PyListIter_Type、PyListRevIter_Type(listobject.h#L21-L22)PySetIter_Type(setobject.h)PyTupleIter_Type(tupleobject.h)PyRangeIter_Type、PyLongRangeIter_Type(rangeobject.h#L19-L20)PyDictIterKey_Type、PyDictRevIterKey_Type、PyDictIterValue_Type、PyDictRevIterValue_Type、PyDictIterItem_Type、PyDictRevIterItem_Type(dictobject.h#L102-L107)PyODictIter_Type(有序字典,odictobject.h)
文档对此给出两条明确的工程约束:
- 不要直接实例化这些类型,应优先调用
PyObject_GetIter()。这与上文iter的实现路径一致:由tp_iter槽或序列回退机制选出正确的迭代器类型,C 扩展作者无需(也不应)自己挑PyListIter_Type还是PyDictIterKey_Type; - 类型与迭代器类型之间没有绑定保证,
range的双迭代器方案(PyRangeIter_Type/PyLongRangeIter_Type按数值大小二选一)就是现成例子,且其他类型未来可能采用类似方案而不提前通知。
在 C 扩展中使用迭代器 API 的实践要点
综合文档语义与源码行为,C 扩展中处理迭代器可以遵循以下模式:
/* 1. 通用取迭代器:永远优先 PyObject_GetIter */
PyObject *it = PyObject_GetIter(obj);
if (it == NULL) {
return NULL; /* 已设置 TypeError */
}
/* 2. 识别迭代器种类(精确类型比较,不会失败) */
if (PySeqIter_Check(it)) {
/* it 是作用于序列的通用迭代器 */
}
else if (PyCallIter_Check(it)) {
/* it 是 callable+sentinel 迭代器 */
}
/* 3. 哨兵迭代器的典型 C 用法:把"无返回值"的 C 回调包一层 */
/* PyObject *it = PyCallIter_New((PyObject *)my_next, Py_None); */
需要注意的几个边界条件:
PySeqIter_New对非序列参数返回 NULL 并报告内部调用错误,调用前可用PySequence_Check预检;- 两个通用迭代器都是 GC 对象且持有成员强引用,耗尽后内部字段置 NULL,但仍可通过
__reduce__被 pickle 为"耗尽态"; - 哨兵比较走
PyObject_RichCompareBool(==语义),比较过程中被调对象若抛出异常,异常会穿透tp_iternext向上传播而不是静默终止; - 对"这个迭代器具体是哪种类型"的判断,仅建议用于调试或断言(如 test_cases.c.h 中对
PyRangeIter_Type的类型断言),不要作为功能分支依据——从range_iter的源码可以推断,range(2**63)这类大值 range 会返回PyLongRangeIter_Type而非PyRangeIter_Type。
小结
iterator.rst 描述的 C API 面由三部分构成:PySeqIter_Type / PySeqIter_New(序列下标迭代)、PyCallIter_Type / PyCallIter_New(哨兵值迭代)、以及一组"仅提供类型识别、不提供函数"的内建迭代器类型对象(enumerate、filter、map、reversed、zip、range、list、dict、set、tuple、bytes、bytearray、odict)。源码层面,Objects/iterobject.c 给出了两个通用迭代器的完整生命周期(GC 跟踪、tp_iternext 终止语义、pickle 支持),Python/bltinmodule.c 展示了 iter() 内建函数如何分发到 PyObject_GetIter 与 PyCallIter_New,Objects/rangeobject.c 则实证了"内建类型与迭代器类型无绑定保证"这一设计约束。对 C 扩展作者而言,核心结论是:构造迭代器一律走 PyObject_GetIter,识别迭代器用 Py*_Check 精确类型宏,而绝不要把具体迭代器类型当作稳定契约。
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 StartedRust0622
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