Skip to content

迭代器与生成器

迭代协议、yield、生成器表达式、send/throw/close。

Updated View as Markdown
For humans

迭代器与生成器

迭代是 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 hi

send 是协程的雏形:生成器可以接收外部数据。Python 协程(async/await)就是建立在生成器机制上的(见 asyncio 篇)。

面试追问

  1. 可迭代对象和迭代器的区别? 可迭代对象有 iter,迭代器有 next。iter() 把前者变后者。迭代器一次性
  2. 生成器为什么省内存? 惰性求值:每次只产出一个值,不保存整个序列。yield 暂停保留局部状态
  3. yield 和 return 的区别? return 结束函数返回值;yield 暂停函数保留状态,可多次产出
  4. send 干什么? 恢复生成器并传入值,yield 表达式求值为该值。双向通信,协程雏形
  5. 列表推导和生成器表达式? 列表推导立即建全部(内存 O(n)),生成器表达式惰性(内存 O(1))但只能遍历一次
Navigation

Type to search…

↑↓ navigate↵ selectEsc close