首页
/ CPython C API 迭代器对象详解:PySeqIter、PyCallIter 与全部内建迭代器类型

CPython C API 迭代器对象详解:PySeqIter、PyCallIter 与全部内建迭代器类型

2026-09-04 22:30:50作者:鲍丁臣Ursa

CPython 在 C 层面提供了两套通用迭代器对象(序列迭代器 PySeqIter_Type 与可调用+哨兵迭代器 PyCallIter_Type),以及一整套覆盖 enumeratefiltermapreversedziprangedictlistset 等内建类型的迭代器类型对象。本文基于 CPython 仓库中 C API 迭代器文档,逐条解析这些 C API 的语义与边界,并结合 iterobject.cbltinmodule.crangeobject.c 等源码,说明每个迭代器对象在解释器内部是如何被创建、推进和销毁的。读完后,你将能在 C 扩展中安全地构造与识别迭代器,理解 iter() 内建函数的两种形式在 C 层的实现路径,并知道何时应该改用 PyObject_GetIter 而不是直接构造具体迭代器类型。

两类通用迭代器对象总览

CPython 提供两个通用迭代器(general-purpose iterator objects),它们与 Python 内建函数 iter() 的两种调用形式一一对应:

  1. 序列迭代器(sequence iterator):作用于任何支持 __getitem__ 方法的序列对象,从下标 0 开始逐项取元素,直到序列对下标操作抛出 IndexError 为止;
  2. 可调用+哨兵迭代器(callable and sentinel iterator):每次迭代调用一个无参可调用对象,将其返回值与哨兵值比较,当返回值等于哨兵时终止迭代。

这两个通用迭代器分别由 PySeqIter_New()PyCallIter_New() 创建,对应的类型对象为 PySeqIter_TypePyCallIter_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_Typeiter 内建函数单参数形式(针对内建序列类型时)所返回迭代器的类型;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,核心流程:

  1. it_seq == NULL(已耗尽),直接返回 NULL,不设置异常——这正是迭代器协议中"返回 NULL 表示 StopIteration"的语义;
  2. 若下标索引达到 PY_SSIZE_T_MAX,抛出 OverflowError("iter index too large")
  3. 调用 PySequence_GetItem(seq, it->it_index) 取元素,成功则索引自增 1;
  4. 若取元素失败且异常为 IndexError StopIteration,则清除异常、置空 it_seq 并释放序列引用,从而结束迭代。

第 4 点比文档描述更宽:除了文档提到的 IndexErrorStopIteration 同样被视为序列终止信号,这是为了兼容那些在越界时抛出 StopIteration 的自定义序列。

__length_hint____reduce____setstate__

序列迭代器还注册了三个方法(iterobject.c#L144-L149):

  • __length_hint__:若原序列支持 len(),返回"剩余元素数"估计值(seqsize - it_index,下限 0);否则返回 NotImplemented
  • __reduce__ / __setstate__:支持 picklingiter_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,关键逻辑:

  1. it_callable == NULL(已耗尽),直接返回 NULL;
  2. _PyObject_CallNoArgs(it->it_callable) 调用可调用对象(无参调用的快速路径);
  3. PyObject_RichCompareBool(it_sentinel, result, Py_EQ)相等性比较而非同一性比较——返回 0 表示"不相等",是最常见的快路径,直接返回该结果;
  4. 比较结果为真(返回 1)或可调用抛出 StopIteration 时,同时清空 it_callableit_sentinel,结束迭代;
  5. 比较过程本身可能抛出异常(返回 -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_Typerange 对象的类型对象;
  • PyRange_Check(PyObject *o):返回 o 是否为 range 实例,函数永远成功。

range 的迭代器选择是实现文档"Other Iterator Objects" 一节警告的典型实例。文档明确写道:"没有保证某个内建类型一定使用某个迭代器类型。例如,遍历 range 会根据 range 的大小使用两种迭代器类型之一,其他类型未来也可能开始使用类似方案,且不会提前警告。"

这段承诺在 rangeobject.c 中得到完整印证。range_iterrangeobject.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);
    ...

即当 startstopstep 与长度都能装进 C long、且迭代过程无溢出风险时,使用基于 long 快速算术的 PyRangeIter_Type;否则回退到基于 PyObject*(任意精度 int)的 PyLongRangeIter_Type。两个类型对象分别定义在 rangeobject.c#L955rangeobject.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 个各类内建对象迭代器的类型对象:

文档对此给出两条明确的工程约束:

  1. 不要直接实例化这些类型,应优先调用 PyObject_GetIter()。这与上文 iter 的实现路径一致:由 tp_iter 槽或序列回退机制选出正确的迭代器类型,C 扩展作者无需(也不应)自己挑 PyListIter_Type 还是 PyDictIterKey_Type
  2. 类型与迭代器类型之间没有绑定保证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(哨兵值迭代)、以及一组"仅提供类型识别、不提供函数"的内建迭代器类型对象(enumeratefiltermapreversedziprangelistdictsettuplebytesbytearrayodict)。源码层面,Objects/iterobject.c 给出了两个通用迭代器的完整生命周期(GC 跟踪、tp_iternext 终止语义、pickle 支持),Python/bltinmodule.c 展示了 iter() 内建函数如何分发到 PyObject_GetIterPyCallIter_NewObjects/rangeobject.c 则实证了"内建类型与迭代器类型无绑定保证"这一设计约束。对 C 扩展作者而言,核心结论是:构造迭代器一律走 PyObject_GetIter,识别迭代器用 Py*_Check 精确类型宏,而绝不要把具体迭代器类型当作稳定契约。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384