Python生成器和协程
生成器
生成器对象是迭代器对象,更是可迭代对象,它实现了__iter__
和__next__
协议
Python通过在函数中使用yield
关键字来定义一个生成器工厂函数,当工厂函数调用时生成一个生成器对象
def gen(a):
print('Started...')
yield a
yield a + 10
print('End...')
yield关键字有两个作用:
- 产出, 当使用
next(Generator)
内置函数它会产出yield expr
语句中expr
的值 - 让步, 程序执行完
yield
右侧的表达式后会暂停执行并将控制权交给调用方
当生成器运行到定义体的结尾会抛出StopIteration
异常。如果函数体内部有return expr
语句,当执行此语句时会抛出StopIteration
异常并结束此生成器,同时return expr
语句中的expr
会被赋值给StopIteration.value
# 这是执行return语句时终端打印的信息,expr即是return的返回值
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: expr
在for
循环语句中迭代生成器对象,不会抛出异常是因为Python
语言内部会处理for
循环和其他迭代上下文(如列表推导、元组拆包等等)中的StopIteration
异常
协程
协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。
通过在生成器函数内部使用Received = yield expr
表达式让普通的生成器进化为协程。调用方通过生成器的send
方法发送消息,并返回yield
产出的值
def coro(a):
print('-> Started: a =', a)
b = yield a
print('-> Received: b =', b)
c = yield a + b
print('-> Received: c =', c)
if __name__ == '__main__':
coroObject = coro(14)
next(coroObject)
coroObject.send(28)
coroObject.send(99)
使用协程,第一步必须使用next
内置函数预激协程。
send(value)
方法将value值赋给Received = yield expr
表达式中的Received
而不是expr
,该方法的返回值是expr
的值。协程之所以要预激,是因为send
方法赋值需要程序先运行到=
,进行赋值
yield from语法
def subGen():
for i in range(5):
rece = yield i
if rece is None:
break
return 'end'
def gen():
while True:
result = yield from subGen()
print(result)
yield from
的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码.
调用方发送的值传递给子生成器并阻塞委派生成器,子生成器只有执行return
语句才会结束阻塞并将return
语句的返回值赋给received
yield from
语法的意义是:
- 协程需要预激,
yield from
会预激子生成器 - 处理子生成器运行结束时抛出的
StopIteration
异常 - 处理子生成器执行
return
语句抛出的StopIteration
异常,并把return语句的返回值绑定给yield from
表达式左侧变量 - 建立双向通道,调用方调用委派生成器的
send
方法时,发送值传递给子生成器Received = yield expr
的Received
变量中;同时将子生成器yield <expr>
中的expr
作为委派生成器send
方法的返回值
委派生成器本身也是生成器,当委派生成器结束时也会抛出StopIteration
异常。所以一般在委派生成器中使用循环,避免抛出异常