6.2.9. yield 表达式

  1. yield_atom ::= "(" yield_expression ")"
  2. yield_expression ::= "yield" [expression_list | "from" expression]

yield 表达式在定义 generator 函数或是 asynchronous generator 的时候才会用到。 因此只能在函数定义的内部使用yield表达式。 在一个函数体内使用 yield 表达式会使这个函数变成一个生成器,并且在一个 async def 定义的函数体内使用 yield 表达式会让协程函数变成异步的生成器。 比如说:

  1. def gen(): # defines a generator function
  2. yield 123
  3. async def agen(): # defines an asynchronous generator function
  4. yield 123

由于它们会对外层作用域造成附带影响,yield 表达式不被允许作为用于实现推导式和生成器表达式的隐式定义作用域的一部分。

在 3.8 版更改: 禁止在实现推导式和生成器表达式的隐式嵌套作用域中使用 yield 表达式。

下面是对生成器函数的描述,异步生成器函数会在 异步生成器函数 一节中单独介绍。

当一个生成器函数被调用的时候,它返回一个迭代器,称为生成器。然后这个生成器来控制生成器函数的执行。当这个生成器的某一个方法被调用的时候,生成器函数开始执行。这时会一直执行到第一个 yield 表达式,在此执行再次被挂起,给生成器的调用者返回 expression_list 的值。挂起后,我们说所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。通过调用生成器的某一个方法,生成器函数继续执行。此时函数的运行就和 yield 表达式只是一个外部函数调用的情况完全一致。恢复后 yield 表达式的值取决于调用的哪个方法来恢复执行。 如果用的是 __next__() (通常通过语言内置的 for 或是 next() 来调用) 那么结果就是 None. 否则,如果用 send(), 那么结果就是传递给send方法的值。

所有这些使生成器函数与协程非常相似;它们 yield 多次,它们具有多个入口点,并且它们的执行可以被挂起。唯一的区别是生成器函数不能控制在它在 yield 后交给哪里继续执行;控制权总是转移到生成器的调用者。

try 结构中的任何位置都允许yield表达式。如果生成器在(因为引用计数到零或是因为被垃圾回收)销毁之前没有恢复执行,将调用生成器-迭代器的 close() 方法. close 方法允许任何挂起的 finally 子句执行。

当使用 yield from <expr> 时,它会将所提供的表达式视为一个子迭代器。 这个子迭代器产生的所有值都直接被传递给当前生成器方法的调用者。 通过 send() 传入的任何值以及通过 throw() 传入的任何异常如果有适当的方法则会被传给下层迭代器。 如果不是这种情况,那么 send() 将引发 AttributeErrorTypeError,而 throw() 将立即引发所传入的异常。

当下层迭代器完成时,被引发的 StopIteration 实例的 value 属性会成为 yield 表达式的值。 它可以在引发 StopIteration 时被显式地设置,也可以在子迭代器是一个生成器时自动地设置(通过从子生成器返回一个值)。

在 3.3 版更改: 添加 yield from <expr> 以委托控制流给一个子迭代器。

当yield表达式是赋值语句右侧的唯一表达式时,括号可以省略。

参见

PEP 255 - 简单生成器

在 Python 中加入生成器和 yield 语句的提议。

PEP 342 - 通过增强型生成器实现协程

增强生成器 API 和语法的提议,使其可以被用作简单的协程。

PEP 380 - 委托给子生成器的语法

引入 yield_from 语法以方便地委托给子生成器的提议。

PEP 525 - 异步生成器

通过给协程函数加入生成器功能对 PEP 492 进行扩展的提议。

6.2.9.1. 生成器-迭代器的方法

这个子小节描述了生成器迭代器的方法。 它们可被用于控制生成器函数的执行。

请注意在生成器已经在执行时调用以下任何方法都会引发 ValueError 异常。

generator.__next__()

开始一个生成器函数的执行或是从上次执行的 yield 表达式位置恢复执行。 当一个生成器函数通过 __next__() 方法恢复执行时,当前的 yield 表达式总是取值为 None。 随后会继续执行到下一个 yield 表达式,其 expression_list 的值会返回给 __next__() 的调用者。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。

此方法通常是隐式地调用,例如通过 for 循环或是内置的 next() 函数。

generator.send(value)

恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。 send() 方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。 当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

generator.throw(type[, value[, traceback]])

在生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。 如果生成器函数没有捕获传入的异常,或引发了另一个异常,则该异常会被传播给调用者。

generator.close()

在生成器函数暂停的位置引发 GeneratorExit。 如果之后生成器函数正常退出、关闭或引发 GeneratorExit (由于未捕获该异常) 则关闭并返回其调用者。 如果生成器产生了一个值,关闭会引发 RuntimeError。 如果生成器引发任何其他异常,它会被传播给调用者。 如果生成器已经由于异常或正常退出则 close() 不会做任何事。

6.2.9.2. 示例

这里是一个简单的例子,演示了生成器和生成器函数的行为:

  1. >>> def echo(value=None):
  2. ... print("Execution starts when 'next()' is called for the first time.")
  3. ... try:
  4. ... while True:
  5. ... try:
  6. ... value = (yield value)
  7. ... except Exception as e:
  8. ... value = e
  9. ... finally:
  10. ... print("Don't forget to clean up when 'close()' is called.")
  11. ...
  12. >>> generator = echo(1)
  13. >>> print(next(generator))
  14. Execution starts when 'next()' is called for the first time.
  15. 1
  16. >>> print(next(generator))
  17. None
  18. >>> print(generator.send(2))
  19. 2
  20. >>> generator.throw(TypeError, "spam")
  21. TypeError('spam',)
  22. >>> generator.close()
  23. Don't forget to clean up when 'close()' is called.

对于 yield from 的例子, 参见“Python 有什么新变化”中的 PEP 380: 委托给子生成器的语法

6.2.9.3. 异步生成器函数

在一个使用 async def 定义的函数或方法中出现的 yield 表达式会进一步将该函数定义为一个 asynchronous generator 函数。

当一个异步生成器函数被调用时,它会返回一个名为异步生成器对象的异步迭代器。 此对象将在之后控制该生成器函数的执行。 异步生成器对象通常被用在协程函数的 async for 语句中,类似于在 for 语句中使用生成器对象。

调用异步生成器的方法之一将返回 awaitable 对象,执行会在此对象被等待时启动。 到那时,执行将前往第一个 yield 表达式,在那里它会再次暂停,将 expression_list 的值返回给等待中的协程。 与生成器一样,挂起意味着局部的所有状态会被保留,包括局部变量的当前绑定、指令的指针、内部求值的堆栈以及任何异常处理的状态。 当执行在等待异步生成器的方法返回下一个对象后恢复时,该函数可以从原状态继续进行,就仿佛 yield 表达式只是另一个外部调用。 恢复执行之后 yield 表达式的值取决于恢复执行所用的方法。 如果使用 __anext__() 则结果为 None。 否则的话,如果使用 asend() 则结果将是传递给该方法的值。

在异步生成器函数中,yield 表达式允许出现在 try 结构的任何位置。 但是,如果一个异步生成器在其被终结(由于引用计数达到零或被作为垃圾回收)之前未被恢复,则then a yield expression within a try 结构中的 yield 表达式可能导致挂起的 finally 子句执行失败。 在此情况下,应由运行该异步生成器的事件循环或任务调度器来负责调用异步生成器-迭代器的 aclose() 方法并运行所返回的协程对象,从而允许任何挂起的 finally 子句得以执行。

为了能处理最终化,事件循环应该定义一个 终结器 函数,它接受一个异步生成器-迭代器且可能调用 aclose() 并执行协程。 这个 终结器 可能通过调用 sys.set_asyncgen_hooks() 来注册。 当首次迭代时,异步生成器-迭代器将保存已注册的 终结器 以便在最终化时调用。 有关For a reference example of a 终结器 方法的参考示例请查看 Lib/asyncio/base_events.py 中实现的 asyncio.Loop.shutdown_asyncgens

yield from <expr> 表达式如果在异步生成器函数中使用会引发语法错误。

6.2.9.4. 异步生成器-迭代器方法

这个子小节描述了异步生成器迭代器的方法,它们可被用于控制生成器函数的执行。

coroutine agen.__anext__()

返回一个可等待对象,它在运行时会开始执行该异步生成器或是从上次执行的 yield 表达式位置恢复执行。 当一个异步生成器函数通过 __anext__() 方法恢复执行时,当前的 yield 表达式所返回的可等待对象总是取值为 None,它在运行时将继续执行到下一个 yield 表达式。 该 yield 表达式的 expression_list 的值会是完成的协程所引发的 StopIteration 异常的值。 如果异步生成器没有产生下一个值就退出,则该可等待对象将引发 StopAsyncIteration 异常,提示该异步迭代操作已完成。

此方法通常是通过 async for 循环隐式地调用。

coroutine agen.asend(value)

返回一个可等待对象,它在运行时会恢复该异步生成器的执行。 与生成器的 send() 方法一样,此方法会“发送”一个值给异步生成器函数,其 value 参数会成为当前 yield 表达式的结果值。 asend() 方法所返回的可等待对象将返回生成器产生的下一个值,其值为所引发的 StopIteration,或者如果异步生成器没有产生下一个值就退出则引发 StopAsyncIteration。 当调用 asend() 来启动异步生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

coroutine agen.athrow(type[, value[, traceback]])

返回一个可等待对象,它会在异步生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值,其值为所引发的 StopIteration 异常。 如果异步生成器没有产生下一个值就退出,则将由该可等待对象引发 StopAsyncIteration 异步。 如果生成器函数没有捕获传入的异常,或引发了另一个异常,则当可等待对象运行时该异常会被传播给可等待对象的调用者。

coroutine agen.aclose()

返回一个可等待对象,它会在运行时向异步生成器函数暂停的位置抛入一个 GeneratorExit。 如果该异步生成器函数正常退出、关闭或引发 GeneratorExit (由于未捕获该异常) 则返回的可等待对象将引发 StopIteration 异常。 后续调用异步生成器所返回的任何其他可等待对象将引发 StopAsyncIteration 异常。 如果异步生成器产生了一个值,该可等待对象会引发 RuntimeError。 如果异步生成器引发任何其他异常,它会被传播给可等待对象的调用者。 如果异步生成器已经由于异常或正常退出则后续调用 aclose() 将返回一个不会做任何事的可等待对象。