迭代器与生成器
迭代是 Python 最核心的抽象之一。面试主线:迭代协议是什么、生成器怎么工作、和列表的区别。
迭代协议
迭代协议: iterable 产出 iterator
- 可迭代对象(iterable):实现
__iter__(或__getitem__),如 list、dict、str、range - 迭代器(iterator):实现
__next__,每次调用返回下一个值,耗尽抛StopIteration for x in obj的本质:iter(obj)拿迭代器,循环调next()直到异常- 迭代器是一次性的:用完不能回头,要重来就重新 iter
自定义迭代器:类实现 __iter__(返回 self)和 __next__。
生成器
生成器是用 yield 的函数,调用它不执行函数体,返回生成器对象:
def countdown(n):
while n > 0:
yield n
n -= 1
g = countdown(3)
next(g) # 3 执行到第一个 yield 暂停
next(g) # 2 从暂停处继续yield暂停函数状态(局部变量全保留),next()恢复执行- 生成器是惰性的:不一次生成全部,内存占用恒定
- 大文件逐行读、无限序列(斐波那契)、流水线处理是典型场景
生成器表达式
squares = (x * x for x in range(10)) # 惰性, 逐个产出
lst = [x * x for x in range(10)] # 立即, 全部在内存同样的语法,圆括号是生成器表达式。省内存但只能遍历一次,且不能下标访问。数据量大或只遍历一次时用生成器。
send/throw/close
生成器对象有三个进阶方法:
| 方法 | 作用 |
|---|---|
send(value) |
恢复执行并把 value 作为 yield 表达式的结果传入(双向通信) |
throw(exc) |
在暂停处抛出异常 |
close() |
在暂停处抛 GeneratorExit,强制结束 |
def echo():
while True:
received = yield # yield 表达式的值来自 send
print(f"got {received}")
g = echo()
next(g) # 启动到第一个 yield
g.send("hi") # got hisend 是协程的雏形:生成器可以接收外部数据。Python 协程(async/await)就是建立在生成器机制上的(见 asyncio 篇)。
面试追问
- 可迭代对象和迭代器的区别? 可迭代对象有 iter,迭代器有 next。iter() 把前者变后者。迭代器一次性
- 生成器为什么省内存? 惰性求值:每次只产出一个值,不保存整个序列。yield 暂停保留局部状态
- yield 和 return 的区别? return 结束函数返回值;yield 暂停函数保留状态,可多次产出
- send 干什么? 恢复生成器并传入值,yield 表达式求值为该值。双向通信,协程雏形
- 列表推导和生成器表达式? 列表推导立即建全部(内存 O(n)),生成器表达式惰性(内存 O(1))但只能遍历一次