迭代:一轮接一轮地做,每轮都比上一轮更进一步。
迭代器(Iterator)是 Python 遍历数据的统一机制:它不一次性把数据搬进内存,而是用一个"指针"按需逐个取出元素。理解迭代器,就理解了
for循环、map、生成器乃至大文件流式处理的内核。
一、为什么需要迭代器
先看一个最直观的对比------读取一个文本文件并逐行处理,有三种写法:
python
# part3-pro.md
# 1. 读入文件行列表,再迭代输出
with open('test.txt') as f:
for line in f.readlines(): # 一次读入整个文件
if not line : break
print(line.upper(), end = '')
# 2. 手工迭代读取文件行
with open('test.txt') as f:
while True:
line = f.readline() # 一次读入一行
if not line : break
print(line.upper(), end = '')
# 3. 使用迭代器读取文件行
with open('test.txt') as f:
for line in f: # 一次读入一行
print(line.upper(), end = '')
三种方式的本质差别:
| 方式 | 内存行为 | 评价 |
|---|---|---|
readlines() |
一次性把整个文件塞进内存 | 文件大到内存装不下时直接崩 |
readline() 手工循环 |
一次读一行,内存友好 | 但要手写 while + 判空,啰嗦 |
for line in f |
迭代器逐行吐出,内部用 C 实现 | 既省内存又快 |
核心结论 :迭代器的价值在于惰性(lazy)------数据不在开始时全部到位,而是"要一个给一个"。这正是它能处理超大文件、无限序列的根本原因。
小结:需要遍历、又不想把数据全装进内存时,迭代器是首选。
二、什么是迭代器:两个协议对象
迭代器机制建立在两个概念之上:
可迭代对象(Iterable) :实现了
__iter__()方法的对象。该方法返回一个迭代器对象 ,可被iter()调用。迭代器对象(Iterator) :实现了
__next__()方法的对象。该方法返回下一个元素,结束时抛出StopIteration,可被next()调用。把迭代器想成"容器上的指针" :初始指向第一个元素;每次调用
__next__()指针前移一位;指向末尾时抛出StopIteration。
迭代器不是容器本身,但有些容器自己也实现了迭代器协议,它同时就是迭代器。
#mermaid-svg-XKuWNv4pfFritU1g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XKuWNv4pfFritU1g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XKuWNv4pfFritU1g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XKuWNv4pfFritU1g .error-icon{fill:#552222;}#mermaid-svg-XKuWNv4pfFritU1g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XKuWNv4pfFritU1g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XKuWNv4pfFritU1g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XKuWNv4pfFritU1g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XKuWNv4pfFritU1g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XKuWNv4pfFritU1g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XKuWNv4pfFritU1g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XKuWNv4pfFritU1g .marker.cross{stroke:#333333;}#mermaid-svg-XKuWNv4pfFritU1g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XKuWNv4pfFritU1g p{margin:0;}#mermaid-svg-XKuWNv4pfFritU1g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster-label text{fill:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster-label span{color:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster-label span p{background-color:transparent;}#mermaid-svg-XKuWNv4pfFritU1g .label text,#mermaid-svg-XKuWNv4pfFritU1g span{fill:#333;color:#333;}#mermaid-svg-XKuWNv4pfFritU1g .node rect,#mermaid-svg-XKuWNv4pfFritU1g .node circle,#mermaid-svg-XKuWNv4pfFritU1g .node ellipse,#mermaid-svg-XKuWNv4pfFritU1g .node polygon,#mermaid-svg-XKuWNv4pfFritU1g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .rough-node .label text,#mermaid-svg-XKuWNv4pfFritU1g .node .label text,#mermaid-svg-XKuWNv4pfFritU1g .image-shape .label,#mermaid-svg-XKuWNv4pfFritU1g .icon-shape .label{text-anchor:middle;}#mermaid-svg-XKuWNv4pfFritU1g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .rough-node .label,#mermaid-svg-XKuWNv4pfFritU1g .node .label,#mermaid-svg-XKuWNv4pfFritU1g .image-shape .label,#mermaid-svg-XKuWNv4pfFritU1g .icon-shape .label{text-align:center;}#mermaid-svg-XKuWNv4pfFritU1g .node.clickable{cursor:pointer;}#mermaid-svg-XKuWNv4pfFritU1g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XKuWNv4pfFritU1g .arrowheadPath{fill:#333333;}#mermaid-svg-XKuWNv4pfFritU1g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XKuWNv4pfFritU1g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XKuWNv4pfFritU1g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XKuWNv4pfFritU1g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XKuWNv4pfFritU1g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XKuWNv4pfFritU1g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XKuWNv4pfFritU1g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XKuWNv4pfFritU1g .cluster text{fill:#333;}#mermaid-svg-XKuWNv4pfFritU1g .cluster span{color:#333;}#mermaid-svg-XKuWNv4pfFritU1g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XKuWNv4pfFritU1g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XKuWNv4pfFritU1g rect.text{fill:none;stroke-width:0;}#mermaid-svg-XKuWNv4pfFritU1g .icon-shape,#mermaid-svg-XKuWNv4pfFritU1g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XKuWNv4pfFritU1g .icon-shape p,#mermaid-svg-XKuWNv4pfFritU1g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XKuWNv4pfFritU1g .icon-shape .label rect,#mermaid-svg-XKuWNv4pfFritU1g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XKuWNv4pfFritU1g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XKuWNv4pfFritU1g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XKuWNv4pfFritU1g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用 iter()
next()
next()
next()
next()无元素时
**可迭代对象(Iterable)**
**迭代器对象(Iterator)**
元素 1
元素 2
元素 3
...
StopIteration
异常
for 循环到底做了什么
很多人以为 for 是"遍历列表"的特例,其实它是迭代器协议的语法糖。for x in obj: 等价于:
python
# 自定义示例:for 循环的等价展开
it = iter(obj) # 1. 拿到迭代器
while True:
try:
x = next(it) # 2. 逐个取值
# ... 循环体 ...
except StopIteration:
break # 3. 取完就停
这就是为什么任何实现了迭代器协议的对象都能用 for 遍历 ------列表、字典、字符串、文件、生成器,无一例外。for 从不为某类数据类型开小灶。
用 isinstance 验证身份
python
# part3-pro.md
from collections.abc import Iterable, Iterator
print(isinstance([1, 2, 3], Iterable)) # True:列表是可迭代对象
print(isinstance([1, 2, 3], Iterator)) # False:列表本身不是迭代器
print(isinstance(iter([1, 2, 3]), Iterator)) # True:iter() 返回迭代器
小结:Iterable 有"产出迭代器的能力",Iterator 额外有"主动吐出下一个元素"的能力。
三、手工调用与自动迭代
源笔记给出的手工迭代案例,正好演示了"指针"如何一步步走:
python
# part3-pro.md
# 1. 列表迭代器
l = [1, 2, 3, 4, 5]
it = iter(l)
# it = l.__iter__() # 另一种方式
try:
while True:
item = next(it)
print(item, end=', ')
except StopIteration as e:
print('end')
# 2. 字典迭代器
d = {'name' : 'z3', 'age' : 18, 'sex' : 'male'}
it = iter(d)
try:
while True:
key = next(it)
print(key, '-->', d[key])
except StopIteration as e:
print(e)
print(type(e))
输出:
text
1, 2, 3, 4, 5, end
name --> z3
age --> 18
sex --> male
<class 'StopIteration'>
而平时写的 for 循环,就是上面这段"手工 iter + next + 捕获 StopIteration"的自动版:
python
# part3-pro.md
# 1. 列表迭代器
l = [1, 2, 3, 4, 5]
for e in l:
print(e, end = ", ")
print('end\n')
# 2. 字典迭代器
d = {'name' : 'z3', 'age' : 18, 'sex' : 'male'}
for key in d:
print(key, '-->', d[key])
小结:手工
next()让你看清迭代全过程;for只是替你包好了StopIteration。
四、自定义迭代器:把协议落到实处
只理解内置类型不够。真正掌握迭代器,是自己写一个迭代器类。规则很简单:
__iter__(self)返回迭代器自身(通常是return self);__next__(self)返回下一个值,没值了就raise StopIteration。
下面用斐波那契数列做例子(只产出不超过 max 的项):
python
# 自定义示例:手写一个斐波那契迭代器
class Fib:
def __init__(self, max):
self.max = max
self.a, self.b = 0, 1
def __iter__(self):
return self # 迭代器返回自身
def __next__(self):
if self.a > self.max:
raise StopIteration # 取完了就抛异常
result = self.a
self.a, self.b = self.b, self.a + self.b
return result
for n in Fib(20):
print(n, end=' ') # 0 1 1 2 3 5 8 13
注意 __iter__ 返回 self 这个细节:它让"可迭代对象"和"迭代器"合二为一,所以 Fib 的对象既能被 for 遍历,也能被 next() 直接驱动。
五、迭代器的两个关键特性
1. 惰性求值(Lazy Evaluation)
迭代器不预先生成所有元素,而是调用一次 __next__ 才计算一次。这意味着:
- 遍历一个亿级元素的序列,内存里只存"当前这一个";
- 可以表示无限序列(如自然数流),因为从不需要"全部生成"。
2. 一次性消耗(一次性流过的水流)
迭代器是有状态的:指针走过就回不去了。同一个迭代器对象遍历一遍后就"耗尽",再遍历得到空:
python
# 自定义示例:迭代器只能走一遍
it = iter([1, 2, 3])
print(list(it)) # [1, 2, 3]
print(list(it)) # [] ------ 已经耗尽
# 可迭代对象(如列表)每次 iter() 都重新造一个迭代器,所以能反复遍历
lst = [1, 2, 3]
print(list(iter(lst))) # [1, 2, 3]
print(list(iter(lst))) # [1, 2, 3]
这正是"可迭代对象"和"迭代器"的实战分水岭:可迭代对象可重复遍历,迭代器一次性。
小结:惰性让它省内存,一次性让它有状态------两者一体两面。
六、生成器:更优雅的迭代器
每次手写 __iter__ / __next__ 太啰嗦。Python 给了"生成器(Generator)":只要函数里有 yield,它就自动变成一个迭代器,状态机由解释器替你维护。
python
# 自定义示例:用生成器重写斐波那契
def fib(max):
a, b = 0, 1
while a <= max:
yield a # 每次走到这里"暂停并返回"
a, b = b, a + b
for n in fib(20):
print(n, end=' ') # 0 1 1 2 3 5 8 13
对比第四节的 Fib 类:逻辑一字不差,但生成器省掉了全部样板代码。生成器本质就是一种迭代器:
python
# 自定义示例:生成器是迭代器
from collections.abc import Iterator
print(isinstance(fib(20), Iterator)) # True
yield 的语义是"产出并暂停"------下次 next() 从暂停处继续,局部变量原样保留。这正是迭代器"指针"思想的自动实现。
七、容器自身是迭代器吗
并非所有容器都"兼职"迭代器。看源笔记的对照:
python
# part3-pro.md
# 1. 迭代器对象不是集合
single = range(1, 10)
# next(single) # TypeError: 'range' object is not an iterator
it1 = iter(single) # 返回一个迭代器
print(next(it1), next(it1), next(it1))
it2 = iter(single) # 返回新的迭代器
print(it1 is it2) # False
print(next(it2), next(it2), next(it2))
# 2. 迭代器对象是集合本身
with open('example.txt', 'r') as f:
print(hasattr(f, '__iter__')) # True
print(hasattr(f, '__next__')) # True
print(iter(f) is f) # True,说明它自身就是迭代器
关键区分:
| 对象 | 是 Iterable | 自身是 Iterator | 说明 |
|---|---|---|---|
list / str / dict |
✅ | ❌ | 每次 iter() 造新迭代器,可重复遍历 |
range |
✅ | ❌ | 同上,next(range) 直接报错 |
文件对象 f |
✅ | ✅ | iter(f) is f,它自己就是迭代器 |
| 生成器 | ✅ | ✅ | 自身即迭代器,一次性 |
小结:
iter(f) is f为True,是判断"容器自身是不是迭代器"的硬标准。
八、常用迭代函数
迭代器不是孤例,Python 标准库里一票函数都返回迭代器,构成"迭代生态":
python
# part3-pro.md + 自定义示例
# 1. map:对每个元素套函数,返回迭代器
print(list(map(str.upper, ['a', 'b']))) # ['A', 'B']
# 2. filter:保留满足条件的元素
print(list(filter(lambda x: x % 2 == 0, range(6)))) # [0, 2, 4]
# 3. zip:并行打包多个可迭代对象
print(list(zip('ab', [1, 2]))) # [('a', 1), ('b', 2)]
# 4. enumerate:带下标迭代
for i, v in enumerate(['x', 'y']):
print(i, v) # 0 x / 1 y
map / filter / zip / enumerate 全部返回惰性迭代器,不会立刻算出全部结果------这就是它们高效的原因。
itertools 模块进一步提供了无限/组合型迭代器,例如:
python
# 自定义示例:itertools 的惰性迭代器
import itertools
# count:无限自然数流(需 islice 截断,否则停不下来)
print(list(itertools.islice(itertools.count(), 5))) # [0, 1, 2, 3, 4]
# chain:把多个可迭代对象串成一条
print(list(itertools.chain([1, 2], [3, 4]))) # [1, 2, 3, 4]
小结:凡是返回迭代器的函数,都天然支持惰性、可串成数据处理管道。
九、常见坑位
| 现象 | 真相 |
|---|---|
for 遍历完再遍历一次,啥也没有 |
那个对象(如生成器、文件对象)是迭代器,一次性耗尽 ;列表则没事,因为每次 iter() 都重建 |
next(range(5)) 直接 TypeError |
range 是可迭代对象,不是迭代器 ,得先 iter(range(5)) |
在 for 循环里 list.remove(x) 会漏元素 |
边遍历边改原容器破坏迭代位置;应遍历副本或用推导式重建 |
自定义 __next__ 里直接 return 当结束 |
应该用 raise StopIteration;return 在迭代器里等价于抛 StopIteration(效果相同,但显式更清晰) |
生成器里 raise StopIteration 想提前结束 |
Python 3.7+ 受 PEP 479 约束,生成器内的 StopIteration 会转为 RuntimeError;应改用 return 结束生成器 |
全文小结
- 迭代器 = 惰性指针 :
__iter__产出迭代器,__next__逐个取值,取完抛StopIteration。 for是语法糖 :底层就是iter()+next()+ 捕获StopIteration,所以任何可迭代对象都能for。- 两类对象分清楚:Iterable 可重复遍历、自身非迭代器(list/str/dict/range);Iterator 一次性、自身即指针(文件对象、生成器)。
- 想省事写迭代器,用生成器 :
yield让函数自动成为迭代器,免去__iter__/__next__样板。 - 惰性 + 一次性是迭代器的双刃剑:省内存、能表达无限序列,但同一迭代器不能反复遍历。