6月2日 01:36

Python 迭代器和生成器有什么区别?yield 和迭代器协议详解

迭代器是实现了 __iter____next__ 方法的对象,生成器是用 yield 关键字自动创建的迭代器。生成器是迭代器的子集——所有生成器都是迭代器,但迭代器不一定是生成器。核心区别:生成器更简洁,且天然支持惰性求值。

迭代器协议

迭代器必须实现两个方法:

  • __iter__:返回 self(让迭代器本身也可迭代)
  • __next__:返回下一个值,没有值时抛出 StopIteration
python
class Countdown: def __init__(self, start): self.current = start def __iter__(self): return self def __next__(self): if self.current <= 0: raise StopIteration self.current -= 1 return self.current + 1 for n in Countdown(3): print(n) # 3, 2, 1

手动实现迭代器要写 __iter____next__、维护状态、处理 StopIteration。代码量多,容易写错。

生成器:迭代器的语法糖

生成器用 yield 关键字,Python 自动实现迭代器协议:

python
def countdown(start): current = start while current > 0: yield current current -= 1 for n in countdown(3): print(n) # 3, 2, 1

调用 countdown(3) 不会执行函数体,而是返回一个生成器对象。每次 next() 执行到 yield 暂停并返回值,下次 next() 从暂停处继续。

3 行代码 vs 10 行代码,效果完全一样。这就是为什么 Python 社区几乎总是用生成器而不是手写迭代器。

生成器表达式

类似列表推导式,但用圆括号,惰性求值:

python
# 列表推导式 — 一次性生成所有数据 squares = [x**2 for x in range(1000000)] # 占用大量内存 # 生成器表达式 — 按需生成 squares = (x**2 for x in range(1000000)) # 几乎不占内存 # 在 sum/max/min 等函数里直接用 total = sum(x**2 for x in range(1000000))

处理大数据时,生成器表达式是列表推导式的直接替代。

yield from:委托给子生成器

yield from 把迭代委托给另一个生成器,避免手写 for 循环:

python
def flatten(nested): for item in nested: if isinstance(item, list): yield from flatten(item) # 递归展开 else: yield item list(flatten([1, [2, [3, 4]], 5])) # [1, 2, 3, 4, 5]

yield from 不只是语法糖——它还正确处理了 send()throw()close() 等生成器方法的传递。

生成器做协程

yield 不只能返回值,还能接收值(通过 send()),这让生成器可以用来实现协程:

python
def accumulator(): total = 0 while True: value = yield total if value is None: break total += value gen = accumulator() next(gen) # 启动生成器,返回 0 gen.send(10) # 返回 10 gen.send(20) # 返回 30

Python 3.5+ 推荐用 async/await 替代这种用法,但理解 yield 的双向通信有助于理解协程原理。

追问

迭代器只能遍历一次吗?

是的。迭代器是有状态的,遍历完就空了。要重新遍历,需要创建新的迭代器。可迭代对象(如列表)每次调用 iter() 都返回新的迭代器。

生成器的内存优势有多大?

读 10GB 日志文件,for line in open("log.txt") 只占几 KB 内存(每次读一行)。readlines() 会把整个文件加载到内存。差距在数据量大时非常显著。

标签:Python