码农高天
for循环本质上是在底层通过GET_ITER指令获取迭代器并调用next方法,因此迭代器自身必须实现返回self的__iter__方法以防止二次遍历报错。
AI-assisted summary of 【python】对迭代器一知半解?看完这个视频就会了。涉及的每个概念,都给你讲清楚! — original source. Check the original for full context.
Source published:
Key ideas
- Python 的 for 循环在字节码层面通过 GET_ITER 指令静默获取迭代器,后续的所有元素步进操作均依赖该迭代器底层驱动。 (0:00)
- 可迭代对象(Iterable)是无状态的容器,负责生成迭代器;迭代器(Iterator)则是记录遍历进度的有状态数据流游标。 (0:00)
- 迭代器的 __next__() 方法在耗尽数据时必须抛出 StopIteration 异常,以此通知 for 循环平稳终止。 (5:00)
- Python 规范要求 Iterator 本身也必须是 Iterable,只需在迭代器中实现返回 self 的 __iter__() 即可支持直接套用 for 循环。 (5:00)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2023 年 10 月 10 日发布。视频简介指出,本期内容旨在系统性讲解 Python 中的迭代器及所有相关核心概念,包括可迭代对象的定义、`__iter__` 与 `__next__` 的机制理解,以及迭代器与 `for` 循环之间的底层关联。
## TL;DR 视频深入剖析了 Python 遍历机制的底层原理,系统辨析了可迭代对象(Iterable)与迭代器(Iterator)的职责边界:前者是具备生成迭代器能力的容器,后者则是记录游标进度的有状态数据流。通过解析字节码指令 `GET_ITER`,作者展示了 `for` 循环如何隐式获取迭代器并驱动 `next()` 调用,同时借助手写链表节点的实战案例,讲解了终止遍历抛出 `StopIteration` 的机制,并指明迭代器自身必须实现 `__iter__()` 返回 `self` 以满足灵活迭代的核心规范。
## Atmosphere & style 整体氛围通俗幽默且极具工程启发性,展现出一种资深开发者抽丝剥茧、拨开概念迷雾的技术教学风格。高天的讲解平实且直击痛点,在对比字节码、手写实现链表迭代器的过程中穿插着对 CPython 官方规范自相矛盾之处的生动调侃,使硬核的底层协议解析听起来轻松流畅、妙趣横生。
## Bulletin - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) Python 的 for 循环在字节码层面通过 GET_ITER 指令静默获取迭代器,后续的所有元素步进操作均依赖该迭代器底层驱动。 - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) 可迭代对象(Iterable)是无状态的容器,负责生成迭代器;迭代器(Iterator)则是记录遍历进度的有状态数据流游标。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 迭代器的 __next__() 方法在耗尽数据时必须抛出 StopIteration 异常,以此通知 for 循环平稳终止。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) Python 规范要求 Iterator 本身也必须是 Iterable,只需在迭代器中实现返回 self 的 __iter__() 即可支持直接套用 for 循环。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) for 循环并非仅能遍历列表等连续索引容器,字典和文件对象同样能借助迭代机制进行遍历。 - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) 可迭代对象(Iterable)是指能够逐个返回其成员的对象,核心特质是可以直接放置在 for 循环的 in 关键字后面。 - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) 迭代器(Iterator)表示一个有状态的数据流对象,可通过反复调用 next() 函数持续获取下一项数据。 - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) Iterable 需实现 __iter__() 返回迭代器,或作为 Sequence 实现 __getitem__() 满足序列协议。 - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) Iterator 的核心条件是必须实现 __next__() 方法,该方法负责返回下一个值或在到头时抛出异常。 - [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) Python 字节码中执行 for 循环时会调用 GET_ITER 指令,首先从可迭代对象中提取对应的迭代器。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 实现迭代器类的 __next__() 必须遵循严格的二选一逻辑:有数据则返回,没有数据则必须 raise StopIteration。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 开发者常在用 iter() 提取迭代器并手动 next() 消费部分元素后继续用 for 循环遍历剩余部分,这要求迭代器必须可迭代。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 若迭代器未实现 __iter__(),直接传入 for 循环时会抛出 TypeError: 'NodeIter' object is not iterable。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 让 Iterator 成为 Iterable 的标准写法是在迭代器内部定义 def __iter__(self): return self。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) CPython 官方文档虽然规定每一个迭代器都必须是可迭代的,但其标准库底层实现中偶尔也存在违背此规则的特例。 - [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 日常开发的核心在于理清容器(Iterable)与状态机游标(Iterator)的职责边界,避免混淆 iter 与 next 的职能。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) for 循环与可迭代对象:表象背后的底层机制
在 Python 中,`for` 循环和迭代器是关系极其紧密但经常被初学者混淆的概念。直观来看,一个简单的列表(`list`)遍历非常符合直觉,因为列表是有序的数据结构,可以从第 0 个、第 1 个、第 2 个逐个提取;然而,并不按数字下标排列的字典(`dictionary`),甚至更为复杂的文件对象,为什么同样能够直接套用 `for... in` 结构进行遍历?这背后依赖于 Python 的两大约束机制:**可迭代对象(Iterable)**与**迭代器(Iterator)**。根据官方文档的定义,可迭代对象是能够逐个返回其成员的对象(如 `list`、`string`、`tuple`、`dict` 等),它最核心的特质就是必须能放在 `for` 循环中 `in` 关键字的后面;而迭代器则是一个表示数据流的对象,可以通过反复调用 `next()` 函数持续从中取出新数据。
```mermaid flowchart TD accTitle: Python for loop 与迭代器工作流程 accDescr: 展示 for 循环在字节码层面如何通过 iter 获取迭代器并循环调用 next 提取数据 subgraph IterableFlow["可迭代对象 (Iterable)"] A["Iterable 对象 (如 list, dict, 文件)"] -->|"实现 __iter__() 或 __getitem__()"| B["iter(obj) 调用 / 字节码 GET_ITER"] end
subgraph IteratorFlow["迭代器 (Iterator)"] B --> C["Iterator 数据流 (有状态)"] C -->|"调用 next()"| D{"是否还有下一个数据?"} D -->|"是"| E["返回元素并推进一步"] D -->|"否"| F["抛出 StopIteration 异常"] end
subgraph ForLoop["for 循环执行"] E -->|"赋值给循环变量"| G["执行循环体代码"] G -->|"继续请求"| C F -->|"捕获异常"| H["正常结束循环"] end ```
从更高的抽象层次(high-level)来看,两者的分工界限极其清晰: - **Iterable(可迭代对象)** 更像是一个容器或数据的保存者,它本身可以是无状态的,完全不需要知道当前数到了哪一个元素,但它必须具备生成一个迭代器的能力。在底层实现上,它要么实现 `__iter__()` 方法,要么属于 Sequence 并实现 `__getitem__()` 方法,从而确保在被内置函数 `iter()` 作用时能顺利返回一个迭代器。 - **Iterator(迭代器)** 则必定是有状态的游标,记录着遍历的进度,但它不需要去实现容器的修改接口。在底层实现上,它必须实现 `__next__()` 方法,以保证被 `next()` 作用时能吐出下一个值。
通过观察 Python 程序的字节码(bytecode)可以发现,在 `LOAD_NAME` 之后紧接着执行的就是一条 `GET_ITER` 指令。也就是说,无论代码表面写得多简单,当解释器执行 `for x in iterable` 时,“所有的 for loop 的实现都是这样的:for loop 首先悄悄的做了一个从 iterable 里面拿到 iterator 的操作”,后续的所有实际步进都是基于该迭代器在底层驱动的。
- **Iterable 核心条件**:实现 `__iter__()` 返回迭代器,或实现 `__getitem__()` 满足序列协议。 - **Iterator 核心条件**:必须实现 `__next__()`,在调用时返回数据流中的下一项。 - **for 循环底层指令**:通过 `GET_ITER` 字节码从栈顶的可迭代对象中提取对应的迭代器。
---
[05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 手写链表迭代器与为什么迭代器自身也必须是 Iterable
为了彻底掌握底层原理,码农高天以 Python 中没有内置但在算法中极其常见的“链表(Linked List)”为例,手写了一个可迭代的 `Node` 类及其配套的迭代器 `NodeIter`。实现一个迭代器类的 `__next__()` 时,开发者在逻辑上必须严格二选一:“第一,如果你这个 iterator 已经到头了,没有办法再给出数据了,你需要 raise 一个 StopIteration 这个 exception;第二,如果还有数据的话,把这个数据 return 回去。”在具体实现中,通过维护 `self.curr_node` 追踪当前节点,如果为 `None` 则抛出 `StopIteration`,否则将指针前移一位并返回当前节点。而在 `Node` 类中定义 `__iter__()` 并返回 `NodeIter(self)`,即可让整个链表顺畅地运行在 `for node in node1:` 的循环中,正确打印出链表各节点的名称。
很多教程和规范都强调 Iterator 除了 `__next__()` 之外还必须实现 `__iter__()`,高天在此解释了背后的根本原因。如果在遍历前需要手动跳过特定元素,开发者很可能会先写 `it = iter(node1)` 手动调用一次 `next(it)` 取出首节点,随后直接写 `for node in it:` 遍历剩余部分。如果迭代器没有实现 `__iter__()`,Python 就会直接抛出类型错误(`TypeError: 'NodeIter' object is not iterable`)。这种写法在逻辑上非常自然,报错却反直觉,因此 Python 官方文档规定:**迭代器本身也必须是一个可迭代对象**。其实现方式极其简单,只需在迭代器类中添加两行代码,让其 `__iter__()` 直接返回 `self` 即可:
```python class NodeIter:
def __init__(self, start_node): self.curr_node = start_node
def __next__(self): if self.curr_node is None: raise StopIteration node = self.curr_node self.curr_node = self.curr_node.next return node
def __iter__(self): return self # 返回自身,使迭代器同时也是 Iterable ```
有趣的是,官方 CPython 文档在制定此规则的同时,甚至自嘲地加了一句附注,承认 CPython 本身也并不总是遵循“每一个 iterator 都必须是 iterable”的铁律,引得高天调侃道:“你说这些人讨厌不讨厌,自己定了一个要求自己不遵守。”但针对日常工程开发,高天亮明了自己的观点: > “我的观点是,首先最好写,因为这件事很简单就两行代码;但是更重要的应该是你应该知道 `iter` 这个函数是干嘛的,`next` 这个函数是干嘛的,他们俩有什么区别,也就是你要分清什么是 Iterable,什么是 Iterator,这两个东西是不一样的。”
只要从本质上理清了容器与状态机的职责差异,面对各种复杂数据结构时,究竟谁该写 `__iter__`、谁该写 `__next__`,便能一目了然。
- **迭代终止协议**:迭代器遍历完毕后必须主动 `raise StopIteration`,`for` 循环捕获该异常后平稳退出。 - **让 Iterator 成为 Iterable 的通用写法**:定义 `def __iter__(self): return self`,使其能无缝传给 `for` 循环、`zip()`、`map()` 等接收 Iterable 的接口。 - **CPython 实现细节**:虽然官方规范要求迭代器实现 `__iter__`,但标准库或 C 底层代码中仍存在极个别未完全遵循此规则的遗留特例。
## Notable quotes > [00:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=0s) 所有的 for loop 的实现都是这样的:for loop 首先悄悄的做了一个从 iterable 里面拿到 iterator 的操作 — 码农高天
> [05:00](https://www.youtube.com/watch?v=vfQdRp_PhhQ&t=300s) 你应该知道 `iter` 这个函数是干嘛的,`next` 这个函数是干嘛的,他们俩有什么区别,也就是你要分清什么是 Iterable,什么是 Iterator,这两个东西是不一样的。 — 码农高天
## Mentioned 码农高天, Python, CPython
## Video description 这期视频讲一下迭代器以及所有相关的概念,包括什么叫可迭代对象,__iter__和__next__的理解,以及迭代器和for loop的关系。
[Source](https://www.youtube.com/watch?v=vfQdRp_PhhQ)