人人都会AI编程

生成器表达式、协程基础

更新时间:2026-07-12

理解了生成器函数的惰性求值机制后,就会很自然地遇到一种更简洁的“轻量级生成器”——生成器表达式

什么是生成器表达式

它的语法和列表推导式几乎一样,只是把方括号 [] 换成圆括号 ()

# 列表推导式:一次性生成整个列表,占内存
squares_list = [x**2 for x in range(10)]

# 生成器表达式:返回一个生成器对象,按需取值
squares_gen = (x**2 for x in range(10))
  • 生成器表达式不会立即计算所有结果,而是返回一个生成器对象。
  • 当你用 next() 或在 for 循环中迭代它时,才一次产生一个值。

为什么要用它——内存友好

假设你有一个百万级别的数据流,想找出所有满足条件的元素并做大写转换。如果用列表推导式:

# 先产生一个百万元素的列表,占大量内存
filtered = [line.upper() for line in huge_file if condition]

而生成器表达式只是声明了一个“计算管道”,内存占用极小:

# 只是一个生成器对象,几乎不占额外内存
filtered = (line.upper() for line in huge_file if condition)
for item in filtered:
    process(item)

最佳实践

  • 当生成器表达式直接作为唯一参数传给函数时,可以省略外层的圆括号,比如:
  sum(x**2 for x in range(1000))          # 比 sum([x**2 for ...]) 更高效
  max(len(word) for word in words)
  
  • 读文件时组合使用生成器表达式和生成器,可以处理大于内存的日志或数据集,例如:
  lines = (line.strip() for line in open('big.log'))
  errors = (line for line in lines if 'ERROR' in line)
  for err in errors:
      print(err)
  

这里管道中的每一步都是惰性的,只有逐条迭代时才触发真正的计算。


8.6.4 协程基础

生成器除了产出值,还可以接收值——这时它就变成了协程。理解协程是后续学习 asyncio 和异步编程的重要前置知识。

从 yield 到双向通信

普通生成器用 yield 向外发送数据;而协程可以通过 yield 接收外界发送的数据,形成双向交互:

def receiver():
    print("开始接收")
    while True:
        msg = yield          # 接收 send 传来的值,赋值给 msg
        print(f"收到: {msg}")

g = receiver()
next(g)                     # 预激活,让生成器执行到 yield 处
g.send("hello")             # 输出:收到: hello
g.send("world")             # 输出:收到: world
g.close()                   # 关闭协程
  • yield 在赋值语句的右边,意味着它既可能传递值出去,也可能接收从外面 send() 进来的值。
  • 使用协程前必须先用 next()send(None) 将其“启动”到第一个 yield 位置,否则会抛出 TypeError

协程能做什么

协程内部可以持有自己的状态,接收数据并做清理、累加等操作,非常适合实现状态机数据流处理器简单调度器

def averager():
    total = 0
    count = 0
    average = None
    while True:
        x = yield average
        total += x
        count += 1
        average = total / count

avg = averager()
next(avg)                # 启动
print(avg.send(10))      # 10.0
print(avg.send(20))      # 15.0
print(avg.send(30))      # 20.0

协程与 asyncio 的区别

  • Python 早期(3.3 之前)的协程就是指上面这种基于生成器的 “yield 协程”
  • 从 Python 3.5 开始,官方引入了 async def / await 的原生协程,搭配 asyncio 实现异步 IO。这是更现代的用法。
  • 但理解生成器协程的 send()throw()close() 等接口,能帮你深度掌握协程调度原理,也为后续学习事件循环打下基础。

一句话总结:生成器表达式让你用最简语法写出惰性数据管道;协程则让生成器从“产出数据”升级为“可收发数据的双向通道”,是实现复杂数据流控制和异步编程的原始基石。