Python 迭代器和生成器有什么区别?yield 和迭代器协议详解
迭代器是实现了 __iter__ 和 __next__ 方法的对象,生成器是用 yield 关键字自动创建的迭代器。生成器是迭代器的子集——所有生成器都是迭代器,但迭代器不一定是生成器。核心区别:生成器更简洁,且天然支持惰性求值。
迭代器协议
迭代器必须实现两个方法:
__iter__:返回 self(让迭代器本身也可迭代)__next__:返回下一个值,没有值时抛出 StopIteration
pythonclass 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 自动实现迭代器协议:
pythondef 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 循环:
pythondef 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()),这让生成器可以用来实现协程:
pythondef 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() 会把整个文件加载到内存。差距在数据量大时非常显著。