iter() 与 next()
iter() 把一个可迭代对象转换成迭代器;next() 从迭代器中取下一个值。
nums = [1, 2, 3, 4]it = iter(nums) # 创建迭代器,相当于"拿到取号机"print(next(it)) # 输出: 1 (取第1个号)print(next(it)) # 输出: 2 (取第2个号)print(next(it)) # 输出: 3print(next(it)) # 输出: 4# print(next(it)) # 如果继续取,会抛出 StopIteration,表示没有更多了
关键点: 迭代器只能往前走,不能回头。就像排队叫号,叫过的号不会再叫。
for 循环遍历
for 循环本质上就是在自动调用 iter() + 反复调用 next(),直到捕获 StopIteration 为止:
nums = [1, 2, 3, 4]it = iter(nums)for x in it: print(x, end=" ")# 输出: 1 2 3 4
上面的 for 循环等价于下面的手动版本:
# for 循环的内部原理(伪代码)it = iter(nums)whileTrue:try: x = next(it) print(x, end=" ")except StopIteration:break# 没有更多元素了,停止循环
用 while + next() 手动遍历
有时候 for 循环不够灵活,可以手动控制:
import sysnums = [1, 2, 3, 4]it = iter(nums)whileTrue:try: print(next(it)) # 每次手动取一个except StopIteration: sys.exit() # 取完了就退出
输出:
1234
自己写一个迭代器类
要让自己的类变成迭代器,需要实现两个魔术方法:
| |
|---|
__iter__(self) | |
__next__(self) | 返回下一个值;没有更多值时抛出 StopIteration |
classMyCounter:"""一个从1开始、无限计数的迭代器"""def__iter__(self): self.current = 1# 初始化计数器return self # 返回自身作为迭代器def__next__(self): value = self.current # 记录当前值 self.current += 1# 指针前进return value # 返回当前值counter = MyCounter()it = iter(counter)print(next(it)) # 1print(next(it)) # 2print(next(it)) # 3print(next(it)) # 4print(next(it)) # 5
注意: 上面这个迭代器会无限计数,不会自动停止。下面介绍如何加停止条件。
StopIteration:何时停下来
StopIteration 是 Python 用来通知"迭代结束"的信号。在 __next__ 里,当达到终止条件时手动 raise 它:
classCountTo:"""从1数到 max_val 后停止"""def__iter__(self): self.current = 1return selfdef__next__(self):if self.current <= self.max_val: # 还没数完 value = self.current self.current += 1return valueelse:raise StopIteration # 数完了,发出结束信号def__init__(self, max_val): self.max_val = max_val# 数到5就停for x in CountTo(5): print(x, end=" ")# 输出: 1 2 3 4 5
执行流程如下:
调用 next() ↓current <= max_val? ├── 是 → 返回 current,current += 1 └── 否 → raise StopIteration → for 循环感知到,自动退出
生成器
yield 关键字
生成器是用 yield 语句定义的特殊函数。它比手写迭代器类要简洁得多。
普通函数 vs 生成器函数的区别:
# 普通函数:一次性返回所有结果(占用大量内存)defget_numbers(n): result = []for i in range(n): result.append(i)return result # 一口气全返回# 生成器函数:每次只产生一个值(按需生成,省内存)defgen_numbers(n):for i in range(n):yield i # 每次暂停在这里,返回一个值
yield 的执行逻辑:
- 调用生成器函数 → 返回一个生成器对象(并不立即执行函数体)
- 每次调用
next() → 函数从上次 yield 的地方继续执行 - 遇到
yield → 暂停执行,把 yield 后面的值返回给调用者 - 函数执行完毕 → 自动抛出
StopIteration
defcountdown(n): print(f"开始从 {n} 倒数")while n > 0:yield n # ← 每次在这里暂停,把 n 返回出去 n -= 1# ← 下次调用 next() 时从这里继续 print("倒数结束!")gen = countdown(5) # 创建生成器对象(函数体还没运行)print(next(gen)) # 输出: 开始从5倒数 \n 5print(next(gen)) # 输出: 4print(next(gen)) # 输出: 3for val in gen: # 继续从4开始遍历剩余的值 print(val) # 输出: 2 1 倒数结束!
输出:
开始从 5 倒数54321倒数结束!
生成器执行流程图
下面是 yield 如何让函数"走走停停"的示意图:
调用 countdown(5) │ ▼ 返回生成器对象 gen(函数体未执行) │ ▼ next(gen) ──→ 函数开始执行 ──→ 遇到 yield 5 ──→ 暂停,返回 5 │ next(gen) ←───────────────── 从 yield 处继续 ←────────┘ │ ▼ 执行 n -= 1 → n=4 → 遇到 yield 4 → 暂停,返回 4 │ ...(重复以上步骤)... │ ▼ n=0,while 条件不满足 → 函数执行完毕 → 自动抛出 StopIteration
实战:斐波那契数列
用生成器实现斐波那契数列,非常优雅:
deffibonacci(n):"""生成斐波那契数列的前 n+1 项""" a, b = 0, 1# a 是当前值,b 是下一个值 count = 0while count <= n:yield a # 产出当前的斐波那契数 a, b = b, a + b # 更新:a 移到 b,b 变成 a+b count += 1# 打印前11项(索引0~10)for num in fibonacci(10): print(num, end=" ")
输出:
0 1 1 2 3 5 8 13 21 34 55
斐波那契数列生成过程(前5步):
迭代器 vs 生成器:对比总结
| | |
|---|
| 定义类,实现 __iter__ 和 __next__ | |
| | |
| | |
| | |
| | |
| | |
一句话总结: 生成器是写迭代器的"快捷方式",代码更短,内存更省,优先选择。
什么时候用生成器?
场景1:处理超大数据,避免内存溢出
# ❌ 普通列表:会把100万个数全部载入内存big_list = [x * 2for x in range(1_000_000)]# ✅ 生成器:每次只生成一个,内存占用几乎为零defdouble_gen(n):for x in range(n):yield x * 2for val in double_gen(1_000_000): process(val) # 边生成边处理
场景2:无限序列
defnatural_numbers():"""无限自然数序列""" n = 0whileTrue: # 永不停止yield n n += 1gen = natural_numbers()print(next(gen)) # 0print(next(gen)) # 1print(next(gen)) # 2# 按需取,永远不会"造完"
场景3:生成器表达式(列表推导式的懒惰版)
# 列表推导式:立即生成所有结果squares_list = [x**2for x in range(10)]# 生成器表达式:只在用到时才计算(把 [] 换成 ())squares_gen = (x**2for x in range(10))print(list(squares_gen)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
总结一下学习路径:
- 理解"迭代器"→ 有
__iter__ 和 __next__ 的对象