Python 的关键字 yield 有哪些用法和用途?
yield 的用法有以下四种常见的情况:
- 一个是生成器,
- 二是用于定义上下文管理器,
- 三是协程,
- 四是配合 from 形成
yield from
用于消费子生成器并传递消息。
这四种用法,其实都源于 yield 所具有的暂停的特性,也就说程序在运行到 yield 所在的位置 result = yield expr
时,先执行 yield expr
将产生的值返回给调用生成器的 caller,然后暂停,等待 caller 再次激活并恢复程序的执行。而根据恢复程序使用的方法不同,yield expr
表达式的结果值 result
也会跟着变化。如果使用 __next()__
来调用,则 yield 表达式的值 result
是 None;如果使用 send()
来调用,则 yield 表达式的值 result
是通过 send 函数传送的值。
我使用到的看代码中使用的是生成器,这里主要记录一下“生成器”的使用。其他使用方式参考:https://www.zhihu.com/question/345210030/answer/841903171
>>> def echo(value=None):
... print("Begin...")
... try:
... while True:
... try:
... value = (yield value)
... except Exception as e:
... value = e
... finally:
... print("Clean up!!!")
...
>>> generator = echo(1)
>>> print(next(generator))
Begin...
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam')
>>> generator.close()
Clean up!!!
作者:wanwan yang
链接:https://www.zhihu.com/question/345210030/answer/841903171
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上面这段代码的说明如下图所示:
- 执行第一个
next(generator)
的时候,也就是预激活生成器,生成器开始执行,打印Begin...
字符串,执行到value = (yield value)
的位置时,首先调用yield value
产生数字1
,然后生成器在 yield 的位置暂停。 - 接着调用第 2 个
next(generator)
的时候,生成器恢复执行,由于使用next()
来调用生成器函数,value
的值会变成None
,因此生成器函数继续执行到yield value
时,会将value
的值None
返回给解释器,然后再次暂停。 - 接着使用
send(2)
方法继续调用生成器,value
接收到传入的数字2
,继续到执行value = (yield value)
,将数字2
返回给解释器后暂停。 - 此后,解释器再次通过
throw(TypeError, "spam")
方法调用,生成器恢复执行,并抛出异常,生成器捕获到异常,并将异常TypeError('spam')
赋值给变量value
,然后程序再次执行到value = (yield value)
,将TypeError('spam')
返回给解释器。 - 最后,程序调用
close()
方法,在生成器函数的位置抛出GeneratorExit
,异常被抛出,生成器正常退出,并最终执行最外层 try 语句对应的 finally 分支,打印输出Clean up
。
生成器
不出意外,你最先遇到 yield 一定会是一个生成器函数里面。生成器是一个用于不断生成数字或者其他类型的值的函数,可以通过 for 循环或者 next() 函数逐一调用。这里需要强调的是,生成器包含的是一个没有赋值的 yield 表达式,所以下面两种形式是等价的:
def integers_1():
for i in range(4):
yield i + 1
def integers_2():
for i in range(4):
value = yield i + 1
这里之所以强调第二种形式,是为了在理解通过 send()
方法发送 value 时,能够更好地理解 yield。同时,也能够更正确地说明,调用生成器返回的值是 yield 关键字右边的表达式 i + 1
的值,而不是 yield 表达式本身的结果值。
>>> for n in integers_1():
... print(n)
...
1
2
3
4
>>> for n in integers_2():
... print(n)
...
1
2
3
4