← 返回题目列表

Python 生成器的 send、throw、close 方法是干什么的?

高频 中等 第 8 / 21 题 更新于 2026/07/31
Python生成器send协程

简化版

**普通生成器只能「产出值」(用 next() 或 for 遍历),但生成器其实是「双向」的——通过 send()throw()close() 三个方法,调用方可以「往生成器里传值/抛异常/关闭它」,让生成器变成一个能「接收」也能「产出」的协程。**三个方法:① send(value)——恢复生成器执行,并把 value 作为「当前暂停的 yield 表达式」的返回值传进去(x = yield 里 x 收到 value),同时返回下一个 yield 的值;next(gen) 等价于 gen.send(None);② throw(exc)——在生成器当前暂停处「抛入」一个异常,让生成器内部能 try/except 处理(或传播出来);③ close()——在生成器暂停处抛入 GeneratorExit,请求生成器结束(可用于清理资源,finally 块会执行)。关键点:第一次必须先 next()(或 send(None))「预激」生成器到第一个 yield 暂停,才能 send 有意义的值。用途:协程(累加器、状态机)、可暂停/恢复的任务、资源清理。核心记忆:send 传值进生成器(yield 表达式的返回值)+ 恢复执行、throw 抛异常进去、close 请求结束;第一次要先 next 预激;这让生成器成为双向协程。

详细版

三个方法

方法作用
gen.send(v)传值进生成器(成为 yield 表达式的值)+ 恢复执行,返回下个 yield
gen.throw(exc)在生成器暂停处抛入异常
gen.close()抛入 GeneratorExit,请求结束(清理资源)
next(gen)等价 gen.send(None)
# send:往生成器里传值
def echo():
    while True:
        received = yield        # yield 表达式接收 send 的值
        print(f"收到: {received}")

g = echo()
next(g)                # 预激:执行到第一个 yield 暂停(必须!)
g.send("hello")        # 收到: hello("hello" 成为 received)
g.send("world")        # 收到: world

# send 同时接收和产出
def accumulator():
    total = 0
    while True:
        x = yield total    # 产出 total,同时 x 接收 send 的值
        total += x

acc = accumulator()
next(acc)              # 预激,产出初始 total=0
print(acc.send(10))    # 传入 10,total=10,产出 10
print(acc.send(5))     # 传入 5,total=15,产出 15

# throw:往生成器里抛异常
def worker():
    try:
        while True:
            x = yield
            print(f"处理 {x}")
    except ValueError:
        print("收到 ValueError,清理中")
        yield "cleaned"

w = worker(); next(w)
w.send(1)              # 处理 1
w.throw(ValueError)    # 收到 ValueError,清理中

# close:请求生成器结束
def resource():
    print("打开资源")
    try:
        while True:
            yield
    finally:
        print("关闭资源")    # close 时 finally 执行

r = resource(); next(r)   # 打开资源
r.close()                 # 关闭资源(GeneratorExit 触发 finally)

# 第一次不预激就 send 值会报错
g2 = echo()
# g2.send("x")            # ✗ TypeError: can't send non-None value to a just-started generator

⚠️ 核心认知:yield 不只是「产出值的出口」,它同时是「接收值的入口」——x = yield value 这个表达式,value 是产出给调用方的,而 x 会在下次 send(v) 时收到 v;正是这个「双向」特性让生成器能当协程用。理解的关键是「暂停点」:生成器执行到 yield 就暂停、把控制权交回调用方;调用方用 next()/send() 恢复它——next() 恢复且让 yield 表达式返回 Nonesend(v) 恢复且让 yield 表达式返回 v。三个方法的分工:send 传值、throw 传异常、close 传「结束信号」(GeneratorExit)。有个必须记住的规矩:新创建的生成器停在「最开始」(还没到第一个 yield),必须先 next()(或 send(None))「预激(prime)」,把它推进到第一个 yield 暂停点,之后 send(有意义的值) 才有地方接收——否则 send(非None) 会报 TypeError(因为还没有暂停的 yield 表达式来接收这个值)。这三个方法是「生成器作为协程」的基础,也是理解 yield from 双向转发和早期 asyncio 的前提。

完整版教学

一、yield 是双向的

先理解 yield 既产出又接收:

普通认知:yield 只是"产出值"
  def gen():
      yield 1        # 产出 1
      yield 2        # 产出 2
  → 用 next() / for 取值

进阶:yield 是"双向"的
  x = yield value
  → value:产出给调用方(out)
  → x:下次 send(v) 时收到 v(in)

  yield 表达式的"值"来自 send:
  next(gen)    → yield 表达式返回 None
  gen.send(v)  → yield 表达式返回 v

执行流程(暂停点):
  生成器执行到 yield 就"暂停",控制权交回调用方
  调用方恢复它:
    next() / send(None):恢复,yield 返回 None
    send(v):恢复,yield 返回 v
  → 生成器从暂停的 yield 处继续,用返回值往下走

图示:
  调用方              生成器
  next(g) ─────────> 执行到 yield value 暂停
          <───────── 产出 value
  g.send(x) ───────> yield 返回 x,继续执行
          <───────── 执行到下个 yield 暂停

所以 yield 双向:x=yield value,value 产出给调用方、x 接收 send 的值;yield 是暂停点

普通认知:yield 只是「产出值」yield 1 产出 1、用 next/for 取值)。进阶:yield 是「双向」的——x = yield valuevalue 产出给调用方(out)、x 下次 send(v) 时收到 v(in)。yield 表达式的「值」来自 sendnext(gen) → yield 表达式返回 None、gen.send(v) → yield 表达式返回 v。执行流程(暂停点):生成器执行到 yield 就「暂停」、控制权交回调用方,调用方恢复它(next/send(None) 恢复且 yield 返回 None、send(v) 恢复且 yield 返回 v)、生成器从暂停的 yield 处继续。理解「yield 双向:x=yield value,value 产出给调用方(out)、x 接收 send 的值(in);yield 表达式的值来自 send(next 返回 None、send(v)返回 v);yield 是暂停点、调用方 next/send 恢复」,就理解了 yield 是双向的。

二、send:传值进生成器

理解 send 的作用:

gen.send(value):
  ① 恢复生成器执行(从当前暂停的 yield 处继续)
  ② 把 value 作为"当前 yield 表达式的返回值"传进去
  ③ 生成器执行到下一个 yield,返回那个 yield 的值

  received = yield          # send(v) 时 received = v
  gen.send("hi")            # received 收到 "hi"

next 和 send 的关系:
  next(gen) 等价 gen.send(None)
  → next 就是"传 None 进去"的 send

预激(priming)——第一次必须 next 或 send(None):
  新生成器停在"最开始"(还没到第一个 yield)
  → 没有暂停的 yield 表达式来接收值
  → 必须先 next() 推进到第一个 yield 暂停
  → 之后 send(有意义的值) 才有地方接收

  g = gen()
  g.send("x")     # ✗ TypeError(刚启动,不能 send 非 None)
  next(g)         # ✓ 预激(推进到第一个 yield)
  g.send("x")     # ✓ 现在可以了

send 同时接收和产出(典型模式):
  def coroutine():
      result = None
      while True:
          x = yield result   # 产出 result,接收 send 的 x
          result = process(x)

自动预激装饰器(常见技巧):
  def coroutine(func):
      def wrapper(*args, **kw):
          g = func(*args, **kw)
          next(g)            # 自动预激
          return g
      return wrapper

所以 send(v)恢复执行+把 v 传给 yield 表达式+返回下个 yield;第一次必须 next 预激

gen.send(value)① 恢复生成器执行(从当前暂停的 yield 处继续)、② 把 value 作为「当前 yield 表达式的返回值」传进去、③ 生成器执行到下一个 yield 返回那个 yield 的值received = yieldgen.send("hi") 时 received 收到 “hi”)。next 和 send 的关系next(gen) 等价 gen.send(None)(next 就是传 None 的 send)。预激(priming)——第一次必须 next 或 send(None):新生成器停在「最开始」(还没到第一个 yield、没有暂停的 yield 表达式接收值),必须先 next() 推进到第一个 yield 暂停、之后 send(有意义的值) 才有地方接收(g.send("x") 刚启动会 TypeError、先 next(g) 预激才行)。自动预激装饰器(常见技巧):包一层自动 next(g)。理解「send(v):恢复执行+把 v 传给当前 yield 表达式+返回下个 yield 的值;next(gen)=send(None);★第一次必须 next 预激(新生成器还没到第一个 yield、不能 send 非 None、否则 TypeError);常用自动预激装饰器」,就掌握了 send。

三、throw:抛异常进生成器

理解 throw 的作用:

gen.throw(异常):在生成器"当前暂停处"抛入一个异常
  → 就像在暂停的 yield 那一行 raise 了这个异常
  → 生成器内部可以 try/except 捕获处理

  def worker():
      try:
          while True:
              x = yield
              process(x)
      except SomeError:
          print("捕获到,清理")   # throw 抛入的异常在这处理

  w.throw(SomeError)   # 在 yield 处抛入 SomeError

处理结果(三种):
  ① 生成器 catch 并继续 yield → throw 返回下个 yield 的值
  ② 生成器 catch 后正常结束 → 抛 StopIteration
  ③ 生成器不 catch → 异常传播回调用方(throw 处抛出)

用途:
  ① 通知生成器"发生了错误,请处理/清理"
  ② 状态机里注入异常状态
  ③ 配合 yield from 转发异常

和普通异常的区别:
  普通异常:生成器内部代码自己出错
  throw:调用方"主动注入"异常到生成器暂停处
  → 一种调用方 → 生成器的"控制信号"

所以 throw(异常)在生成器暂停处抛入异常,内部可 try/except 处理,是控制信号

gen.throw(异常):在生成器「当前暂停处」抛入一个异常——就像在暂停的 yield 那一行 raise 了这个异常、生成器内部可以 try/except 捕获处理(w.throw(SomeError) 在 yield 处抛入、被内部 except SomeError 处理)。处理结果(三种)① 生成器 catch 并继续 yield → throw 返回下个 yield 的值、② 生成器 catch 后正常结束 → 抛 StopIteration、③ 生成器不 catch → 异常传播回调用方(throw 处抛出)用途① 通知生成器发生了错误请处理/清理、② 状态机里注入异常状态、③ 配合 yield from 转发异常和普通异常的区别:普通异常是生成器内部代码自己出错、throw 是调用方「主动注入」异常到生成器暂停处(一种调用方→生成器的控制信号)。理解「throw(异常)在生成器暂停处抛入异常(像在 yield 那行 raise),内部可 try/except 处理;结果:catch 继续/catch 结束抛 StopIteration/不 catch 传播回调用方;是调用方注入的控制信号」,就掌握了 throw。

四、close:请求生成器结束

理解 close 和资源清理:

gen.close():请求生成器结束
  → 在生成器暂停处抛入 GeneratorExit 异常
  → 生成器应该"结束"(清理资源后退出)

行为:
  ① 在暂停的 yield 处抛入 GeneratorExit
  ② 生成器的 finally 块会执行(清理资源)
  ③ 生成器正常结束(GeneratorExit 被吞掉,不传播)

资源清理模式(最重要的用途):
  def resource():
      conn = open_connection()    # 获取资源
      try:
          while True:
              data = yield
              conn.send(data)
      finally:
          conn.close()            # close() 时 finally 执行,清理

  r = resource(); next(r)
  # ... 使用 ...
  r.close()                       # 触发 finally,关闭 conn

GeneratorExit 的处理:
  ① 生成器不捕获 → 正常结束(推荐)
  ② 生成器捕获 GeneratorExit 后可清理,但:
     - 不能再 yield(会 RuntimeError: generator ignored GeneratorExit)
     - 应该让它结束(re-raise 或正常退出)

for 循环 / with 也会触发清理:
  生成器被垃圾回收时也会调用 close(触发 finally)
  → 但显式 close 或 with 更可靠

自动清理(contextlib):
  from contextlib import closing
  with closing(gen()) as g:
      ...                    # 退出 with 时自动 g.close()

所以 close()抛入 GeneratorExit 请求结束,finally 块执行清理资源;不能捕获后再 yield

gen.close():请求生成器结束——在生成器暂停处抛入 GeneratorExit 异常、生成器应该「结束」(清理资源后退出)。行为① 在暂停的 yield 处抛入 GeneratorExit、② 生成器的 finally 块会执行(清理资源)、③ 生成器正常结束(GeneratorExit 被吞掉不传播)资源清理模式(最重要的用途)try: while True: yield finally: conn.close()——close() 时 finally 执行清理。GeneratorExit 的处理:① 生成器不捕获 → 正常结束(推荐)、② 捕获 GeneratorExit 后可清理但不能再 yield(会 RuntimeError: generator ignored GeneratorExit、应该让它结束)。for/with 也会触发清理:生成器被垃圾回收时也会调用 close(但显式 close 或 with 更可靠、contextlib.closing 自动 close)。理解「close()抛入 GeneratorExit 请求结束,finally 块执行清理资源;不捕获则正常结束(推荐)、捕获后不能再 yield(RuntimeError);垃圾回收也触发、但显式 close/contextlib.closing 更可靠」,就掌握了 close。

五、协程应用

理解生成器协程的实际应用:

生成器协程的应用:

① 累加器/运行统计(有状态):
   def running_average():
       total = 0; count = 0; avg = None
       while True:
           x = yield avg          # 产出当前均值、接收新数据
           total += x; count += 1
           avg = total / count
   ra = running_average(); next(ra)
   ra.send(10)  # 10.0
   ra.send(20)  # 15.0
   → 生成器保持状态,逐步接收数据、产出结果

② 状态机:
   用 yield 暂停等待事件、send 注入事件、状态转移

③ 管道(数据处理链):
   生成器串成管道,数据 send 进去、逐级处理

④ 可暂停/恢复的任务:
   yield 暂停任务、send 恢复并传入新参数

⑤ 早期 asyncio 的基础:
   yield 暂停等待 IO、send 恢复(现在用 async/await)

预激的处理(协程必做):
  用装饰器自动预激:
  @coroutine
  def my_coro(): ...      # 自动 next() 预激

协程 vs 迭代(区别):
  迭代生成器:单向产出值(next/for)
  协程生成器:双向(send 传入、yield 产出、throw/close 控制)

现代替代:
  纯数据流 → 普通生成器 + yield
  异步 IO → async/await(不用手写生成器协程)
  → 手写 send/throw 协程现在少见,但理解它是懂 async 前提

所以协程应用:累加器/状态机/管道/可暂停任务;预激用装饰器;现代异步用 async/await

生成器协程的应用① 累加器/运行统计(有状态)x = yield avg 产出当前均值、接收新数据、生成器保持状态逐步处理)、② 状态机(yield 暂停等待事件、send 注入事件、状态转移)、③ 管道(数据处理链)(生成器串成管道、数据 send 进去逐级处理)、④ 可暂停/恢复的任务⑤ 早期 asyncio 的基础(yield 暂停等待 IO、send 恢复、现在用 async/await)。预激的处理(协程必做):用装饰器自动预激。协程 vs 迭代(区别):迭代生成器单向产出值(next/for)、协程生成器双向(send 传入、yield 产出、throw/close 控制)。现代替代:纯数据流用普通生成器、异步 IO 用 async/await(手写 send/throw 协程现在少见、但理解它是懂 async 前提)。理解「协程应用:累加器/状态机/管道/可暂停任务;预激用装饰器;协程双向(send/yield/throw/close)vs 迭代单向;现代异步用 async/await、理解生成器协程是懂 async 前提」,就掌握了协程应用。

六、总结与实践

总结三个方法:

核心:yield 是双向的(x = yield value)
  value 产出给调用方、x 接收 send 的值
  → 生成器能当"协程"(接收+产出)

三个方法:
  send(v):恢复+把 v 传给 yield 表达式+返回下个 yield
           (next = send(None))
  throw(e):在暂停处抛入异常(内部可 try/except)
  close():抛入 GeneratorExit 请求结束(finally 清理)

关键规矩:
  第一次必须 next()/send(None) 预激
  (新生成器还没到第一个 yield,不能 send 非 None)

用途:
  send:协程传值(累加器、状态机、管道)
  throw:注入异常/错误信号
  close:清理资源(finally 执行)

注意:
  捕获 GeneratorExit 后不能再 yield(RuntimeError)
  预激用装饰器自动化
  现代异步用 async/await(不手写生成器协程)

核心总结:
  send 传值进生成器(yield 表达式的值)+ 恢复
  throw 抛异常进去、close 请求结束
  第一次要预激,让生成器成为双向协程

所以 send 传值/throw 抛异常/close 结束,第一次预激,生成器成双向协程

核心:yield 是双向的x = yield value,value 产出给调用方、x 接收 send 的值、生成器能当协程)。三个方法send(v)(恢复+把 v 传给 yield 表达式+返回下个 yield,next = send(None))、throw(e)(暂停处抛入异常、内部可 try/except)、close()(抛入 GeneratorExit 请求结束、finally 清理)。关键规矩:第一次必须 next()/send(None) 预激。用途:send(协程传值)、throw(注入异常)、close(清理资源)。注意:捕获 GeneratorExit 后不能再 yield、预激用装饰器、现代异步用 async/await。理解「send 传值+恢复(next=send(None))/throw 抛异常/close 请求结束(finally 清理);第一次预激;用途累加器/状态机/清理;async/await 是现代替代」,就掌握了总结与实践。

记忆钩子:「普通生成器只『产出值』(next/for),但生成器是双向的——yield 既是产出出口也是接收入口(x=yield value:value 产出给调用方、x 在下次 send(v)时收到 v),通过三个方法让生成器成为『协程』:①send(value)——恢复生成器执行,把 value 作为『当前暂停的 yield 表达式的返回值』传进去(x=yield 里 x 收到 value),并返回下一个 yield 的值;★next(gen)等价 gen.send(None);②throw(exc)——在生成器当前暂停处『抛入』异常,内部可 try/except 处理(或传播回调用方),是调用方注入的控制信号;③close()——在暂停处抛入 GeneratorExit 异常请求结束,生成器的 finally 块会执行(清理资源),捕获后不能再 yield(否则 RuntimeError);★关键规矩:第一次必须先 next()或 send(None)『预激(prime)』,把新生成器推进到第一个 yield 暂停点,之后 send(有意义的值)才有 yield 表达式来接收,否则 send(非 None)报 TypeError;用途:协程(累加器/运行统计/状态机/数据管道)、可暂停恢复的任务、资源清理;常用自动预激装饰器;现代异步编程用 async/await 取代了手写生成器协程,但理解 send/yield 双向是懂 async 的前提」

七、常见误区与追问

  • 误区:yield 只能产出值,不能接收值。 yield 是双向的——x = yield value 里,value 是产出给调用方的(用 next/for 取),而 x 会在调用方下次 send(v) 时收到 v(yield 表达式的「返回值」来自 send);正是这个「既产出又接收」的特性,让生成器能当协程用(累加器、状态机等),调用方通过 send 把数据「喂」进生成器、生成器通过 yield 把结果「吐」出来。
  • 误区:新创建的生成器可以直接 send(值)。 不行——新生成器停在「最开始」(还没执行到第一个 yield、没有暂停的 yield 表达式来接收值),直接 gen.send("x")(非 None)会抛 TypeError: can't send non-None value to a just-started generator;必须先用 next(gen)gen.send(None)「预激(prime)」、把它推进到第一个 yield 暂停点,之后 send 有意义的值才有地方接收;协程代码常用一个装饰器自动完成预激。
  • 误区:next(gen) 和 gen.send(None) 不一样。 完全等价——next(gen) 就是 gen.send(None):都恢复生成器执行、让当前 yield 表达式的返回值为 None、并返回下一个 yield 产出的值;区别只是 send 还能传非 None 的值进去(send(v) 让 yield 表达式返回 v);所以「预激」既可以用 next(gen) 也可以用 gen.send(None)
  • 误区:close() 只是简单地让生成器停止,不用管资源。 恰恰相反——close() 的主要价值就是「触发资源清理」:它在生成器暂停处抛入 GeneratorExit 异常,如果生成器里用了 try/finally,finally 块会执行(关闭连接、释放锁、清理临时文件等);所以正确的资源型生成器应该把清理放在 finally 里、依赖 close(或 with contextlib.closing、或被垃圾回收时的自动 close)来触发;注意捕获了 GeneratorExit 后不能再 yield(否则抛 RuntimeError: generator ignored GeneratorExit)、应该让生成器正常结束。
  • 追问:send、throw、close 三个方法分别是干什么的,怎么配合? 它们是调用方向生成器发送三种「信号」:① send(value)——恢复生成器执行、把 value 作为当前暂停的 yield 表达式的返回值传进去(x = yield 里 x 收到 value)、并返回下一个 yield 产出的值,是「传数据进去」;② throw(exc)——在生成器当前暂停的 yield 处抛入一个异常,让生成器内部能 try/except 处理(或让异常传播回调用方),是「传错误信号进去」;③ close()——在暂停处抛入 GeneratorExit 请求生成器结束、触发 finally 清理,是「传结束信号进去」;配合起来,生成器就成了一个能接收数据(send)、能处理注入的异常(throw)、能被优雅关闭并清理(close)的双向协程;使用前都要先 next() 预激。
  • 追问:怎么用生成器实现一个「运行平均值」的协程? 用 send 传入数据、yield 产出当前均值:def running_average(): total = 0; count = 0; avg = None; while True: x = yield avg; total += x; count += 1; avg = total / count——创建后先 ra = running_average(); next(ra) 预激(执行到第一个 yield、产出初始的 avg=None),之后每次 ra.send(10) 就把 10 传给 x、更新 total 和 count、算出新均值并通过 yield 产出(ra.send(10) 返回 10.0、ra.send(20) 返回 15.0);这个协程「记住」了 total 和 count 的状态、能持续接收数据流并实时产出统计结果,比每次重新计算高效;预激这一步可以用装饰器自动化。
  • 追问:既然有生成器协程(send/throw),为什么还要 async/await? 生成器协程(用 yield + send/throw/close 手写)虽然能实现协程,但有明显问题:① 语法笨拙——要手动预激、要区分「产出值的 yield」和「等待的 yield」、双向语义容易混淆;② 和普通生成器(迭代用)语法上无法区分,容易搞错;③ 早期 asyncio 用「生成器 + yield from」实现异步,写起来繁琐、可读性差;Python 3.5 引入 async def / await(PEP 492)就是为了给「协程/异步」一套专门的、清晰的语法——async def 明确声明这是协程、await 明确表示「等待一个可等待对象」,语义清楚、不和迭代混淆、还能配合 async for / async with;所以现在异步编程一律用 async/await,手写 send/throw 生成器协程基本只在特殊场景或理解原理时才用;但理解「yield 双向 + send」是理解协程本质和 async/await 由来的基础。

八、加强记忆

普通生成器只「产出值」(next/for),但生成器是双向的——yield 既是产出出口也是接收入口(x = yield value:value 产出给调用方、x 在下次 send(v) 时收到 v),通过三个方法让生成器成为「协程」:send(value)——恢复生成器执行、把 value 作为「当前暂停的 yield 表达式的返回值」传进去(x = yield 里 x 收到 value)、并返回下一个 yield 的值(next(gen) 等价 gen.send(None));throw(exc)——在生成器当前暂停处「抛入」异常、内部可 try/except 处理(或传播回调用方);close()——在暂停处抛入 GeneratorExit 请求结束、生成器的 finally 块会执行(清理资源,捕获后不能再 yield 否则 RuntimeError)。关键规矩:第一次必须先 next()send(None)「预激(prime)」、把新生成器推进到第一个 yield 暂停点,之后 send(有意义的值) 才有 yield 表达式接收,否则 send(非None) 报 TypeError。用途:协程(累加器/运行统计/状态机/数据管道)、可暂停恢复的任务、资源清理;现代异步编程用 async/await 取代了手写生成器协程,但理解 send/yield 双向是懂 async 的前提。一句话「yield 双向(x=yield value:产出 value、x 收 send 的值);send 传值进生成器+恢复(next=send(None))、throw 抛异常进暂停处、close 抛 GeneratorExit 请求结束(finally 清理);第一次必须 next 预激(否则 send 非 None 报 TypeError);让生成器成双向协程,现代异步用 async/await」。