Python
迭代器 · 生成器
对比可迭代对象、迭代器与生成器,整理 iter、next、yield、send 和生成器自动启动装饰器的用法。
迭代器 · 生成器
一、可迭代对象 vs 迭代器对象
1. 通俗类比
- 可迭代对象(Iterable) = 一本可以翻的书
- 迭代器(Iterator) = 一个会自己翻页的书签
书本身不会自己翻页,你得找个书签帮你翻。书签记得”我翻到第几页了”。
2. 可迭代对象(Iterable)
能被 for 循环遍历的东西,都是可迭代对象。
常见例子:
1 2 3 4 5 6 7
| 列表 [1, 2, 3] 字符串 "abc" 元组 (1, 2, 3) 字典 {"a": 1} 集合 {1, 2, 3} range(5) 文件对象 open("a.txt")
|
判断方法:看有没有 __iter__() 方法。
1 2
| nums = [1, 2, 3] print(hasattr(nums, '__iter__'))
|
3. 迭代器对象(Iterator)
既能被 for 遍历,又能用 next() 一个一个取值的对象。
两个核心方法:
| 方法 |
作用 |
__iter__() |
返回自己 |
__next__() |
返回下一个值,取完抛 StopIteration |
1 2 3
| nums = [1, 2, 3] it = iter(nums) print(hasattr(it, '__next__'))
|
4. 体验
1 2 3 4 5 6 7
| nums = [1, 2, 3] it = iter(nums)
print(next(it)) print(next(it)) print(next(it)) print(next(it))
|
5. for 循环内部做了什么
1 2
| for x in [1, 2, 3]: print(x)
|
等价于:
1 2 3 4 5 6 7
| it = iter([1, 2, 3]) while True: try: x = next(it) print(x) except StopIteration: break
|
6. 两者关系
可迭代对象 ≠ 迭代器,但迭代器一定是可迭代对象。
7. 对比表
| 特性 |
可迭代对象 |
迭代器 |
能被 for 遍历 |
✅ |
✅ |
有 __iter__() |
✅ |
✅ |
有 __next__() |
❌ |
✅ |
能 next() 取值 |
❌ |
✅ |
| 记住遍历位置 |
❌ |
✅ |
| 能重复遍历 |
✅ |
❌(一次性) |
| 例子 |
列表、字符串、字典 |
iter([1,2,3])、生成器 |
8. 迭代器是一次性的!!
1 2 3 4 5
| nums = [1, 2, 3] it = iter(nums)
print(list(it)) print(list(it))
|
9. 自己造一个
可迭代对象:
1 2 3 4 5 6 7 8 9
| class MyList: def __init__(self, data): self.data = data
def __iter__(self): return iter(self.data)
for x in MyList([1, 2, 3]): print(x)
|
迭代器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Counter: def __init__(self, n): self.n = n self.i = 0
def __iter__(self): return self
def __next__(self): if self.i >= self.n: raise StopIteration self.i += 1 return self.i
for x in Counter(3): print(x)
|
10. 总结
可迭代对象 = 能被遍历的东西(书)
迭代器 = 能被遍历 + 会自己往下走 + 一次性(书签)
可迭代是”资格”,迭代器是”能力”。
二、生成器(Generator)
1. 通俗类比
- 普通函数 = 一次性把菜全做完端上来(列表)
- 生成器 = 现做现上,你吃一口我做一道
2. 生成器是什么
生成器是一种特殊的迭代器。
特点:
- 用
yield 代替 return
- 不会一次性算完,而是”要一个给一个”(惰性求值)
一句话:生成器 = 会”暂停”的函数 + 自动成为迭代器。
3. 创建方式一:生成器函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def make_gen(): yield 1 yield 2 yield 3
g = make_gen() print(next(g)) print(next(g)) print(next(g)) print(next(g))
for x in make_gen(): print(x)
|
4. 创建方式二:生成器表达式
1 2 3
| nums = (x * 2 for x in range(5)) print(next(nums)) print(next(nums))
|
记忆点:
[] → 列表推导式 → 全部算完
() → 生成器表达式 → 用到才算
5. yield 到底做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def demo(): print("开始") yield 1 print("继续") yield 2 print("结束")
g = demo() print("=== 还没执行 ===") print(next(g)) print("=== 第一次暂停后 ===") print(next(g)) print("=== 第二次暂停后 ===") print(next(g))
|
输出:
1 2 3 4 5 6 7 8 9
| === 还没执行 === 开始 1 === 第一次暂停后 === 继续 2 === 第二次暂停后 === 结束 StopIteration
|
yield = 暂停 + 返回值 + 记住现场。
6. 为什么用生成器:省内存
用列表(爆内存):
1 2 3 4 5
| def make_nums(): result = [] for i in range(100000000): result.append(i) return result
|
用生成器(无压力):
1 2 3 4 5 6
| def make_nums(): for i in range(100000000): yield i
for n in make_nums(): print(n)
|
7. 经典例子:斐波那契
1 2 3 4 5 6 7 8
| def fib_gen(n): a, b = 0, 1 for _ in range(n): yield a a, b = b, a + b
for x in fib_gen(10): print(x)
|
8. 两个常用方法
send():给生成器发值
1 2 3 4 5 6 7 8 9 10
| def demo(): x = yield 1 print("收到:", x) y = yield 2 print("收到:", y)
g = demo() print(next(g)) print(g.send(100)) print(g.send(200))
|
close():关闭生成器
1 2 3
| g = demo() next(g) g.close()
|
9. 特点总结
| 特点 |
说明 |
用 yield 定义 |
有 yield 就是生成器函数 |
| 惰性求值 |
用到才算 |
| 一次性 |
用完就没了 |
| 自动是迭代器 |
能用 next() 和 for |
| 状态保持 |
暂停后记住现场 |
10. 使用场景
| 场景 |
说明 |
| 数据量巨大 |
读 10G 日志,一行一行处理 |
| 无限序列 |
所有自然数 |
| 管道式处理 |
生成器套生成器 |
| 只要遍历一次 |
不需要保存全部数据 |
实战:读大文件
1 2 3 4 5 6 7
| def read_big_file(path): with open(path) as f: for line in f: yield line
for line in read_big_file("huge.log"): process(line)
|
11. 对比表
| 对比项 |
列表 |
生成器 |
| 内存 |
全部存起来 |
只存当前 |
| 速度 |
全部算完才能用 |
边算边用 |
| 重复遍历 |
✅ |
❌ |
| 适用 |
数据小、反复用 |
数据大、只用一次 |
12. 总结
有 yield 就是生成器函数,圆括号推导式就是生成器表达式。
yield = 暂停 + 返回值 + 记住现场。
生成器 = 惰性求值 = 要一个算一个 = 省内存。
三、生成器的两种调用方式
1. 手动调用
基础:用 next()
1 2 3 4 5 6 7 8 9 10
| def gen(): yield 1 yield 2 yield 3
g = gen() print(next(g)) print(next(g)) print(next(g)) print(next(g))
|
半自动:用 for
1 2
| for x in gen(): print(x)
|
真正的手动:用 send() 双向通信
1 2 3 4 5 6 7 8 9
| def gen(): while True: x = yield print(f"收到:{x}")
g = gen() next(g) g.send("你好") g.send("世界")
|
典型场景:实时平均值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def averager(): total = 0 count = 0 avg = None while True: x = yield avg total += x count += 1 avg = total / count
g = averager() next(g) print(g.send(10)) print(g.send(20)) print(g.send(30))
|
2. 用装饰器调用
问题:用 send() 前必须先 next() 启动,容易忘。
1 2
| g = averager() g.send(10)
|
解决:自动启动装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from functools import wraps
def auto_start(func): @wraps(func) def wrapper(*args, **kwargs): g = func(*args, **kwargs) next(g) return g return wrapper
@auto_start def averager(): total = 0 count = 0 avg = None while True: x = yield avg total += x count += 1 avg = total / count
g = averager() print(g.send(10))
|
官方例子:@contextmanager
1 2 3 4 5 6 7 8 9 10 11 12
| from contextlib import contextmanager
@contextmanager def my_open(path): print("打开文件") f = open(path) yield f f.close() print("关闭文件")
with my_open("a.txt") as f: print("使用文件")
|
3. 对比表
| 对比项 |
手动调用 |
装饰器调用 |
| 谁控制流程 |
你亲自 next/send |
装饰器帮你启动/包装 |
| 启动步骤 |
必须手动 next(g) |
自动完成 |
| 代码复杂度 |
灵活但要小心 |
简洁,逻辑清晰 |
| 典型用途 |
协程、双向通信 |
上下文管理器、自动启动 |
4. 总结
手动调用:你自己用 next() 和 send() 驱动生成器。
装饰器调用:装饰器帮你自动启动或包装生成器。
本质:都是”驱动生成器”,一个亲手,一个代劳。