协程的底层架构是在pep342 中定义,并在python2.5 实现的。
python2.5 中,yield关键字可以在表达式中利用,而且天生器API中增加了 .send(value)方法。天生器可以利用.send(...)方法发送数据,发送的数据会成为天生器函数中yield表达式的值。
协程是指一个过程,这个过程与调用方协作,产出有调用方供应的值。因此,天生器可以作为协程利用。

除了 .send(...)方法,pep342 和添加了 .throw(...)(让调用方抛出非常,在天生器中处理)和.close()(终止天生器)方法。
python3.3后,pep380对天生器函数做了两处改动:
天生器可以返回一个值;以前,如果天生器中给return语句供应值,会抛出SyntaxError非常。
引入yield from 语法,利用它可以把繁芜的天生看重构成小型的嵌套天生器,省去之前把天生器的事情委托给子天生器所需的大量模板代码。
协程天生器的基本行为
首先解释一下,协程有四个状态,可以利用inspect.getgeneratorstate(...)函数确定:
GEN_CREATED # 等待开始实行
GEN_RUNNING # 阐明器正在实行(只有在多线程运用中才能看到这个状态)
GEN_SUSPENDED # 在yield表达式处停息
GEN_CLOSED # 实行结束
import inspect # 协程利用天生器函数定义:定义体中有yield关键字。 def simple_coroutine(): print('-> coroutine started') # yield 在表达式中利用;如果协程只须要从客户那里吸收数据,yield关键字右边不须要加表达式(yield默认返回None) x = yield print('-> coroutine received:', x)my_coro = simple_coroutine()# 和创建天生器的办法一样,调用函数得到天生器工具。 print(inspect.getgeneratorstate(my_coro)) # 协程处于 GEN_CREATED (等待开始状态)my_coro.send(None) # 首先要调用next()函数,由于天生器还没有启动,没有在yield语句处停息,以是开始无法发送数据 # 发送 None 可以达到相同的效果 my_coro.send(None) next(my_coro)# 此时协程处于 GEN_SUSPENDED (在yield表达式处停息) print(inspect.getgeneratorstate(my_coro)) # 调用这个方法后,协程定义体中的yield表达式司帐算出42;现在协程会规复,一贯运行到下一个yield表达式,或者终止。#! -- coding: utf-8 --
my_coro.send(42)print(inspect.getgeneratorstate(my_coro))
运行上述代码,输出结果如下
GEN_CREATED-> coroutine startedGEN_SUSPENDED-> coroutine received: 42# 这里,掌握权流动到协程定义体的尾部,导致天生器像往常一样抛出StopIteration非常
Traceback (most recent call last): File \"大众/Users/gs/coroutine.py\"大众, line 18, in <module> my_coro.send(42)StopIteration
send方法的参数会成为停息yield表达式的值,以是,仅当协程处于停息状态是才能调用send方法。
如果协程还未激活(GEN_CREATED 状态)要调用next(my_coro) 激活协程,也可以调用my_coro.send(None)
如果创建协程工具后立即把None之外的值发给它,会涌现下述缺点:
>>> my_coro = simple_coroutine()
>>> my_coro.send(123)Traceback (most recent call last): File \"大众/Users/gs/coroutine.py\"大众, line 14, in <module> my_coro.send(123)TypeError: can't send non-None value to a just-started generator
仔细看缺点
can't send non-None value to a just-started generator
最先调用next(my_coro) 这一步常日称为”预激“(prime)协程---即,让协程向前实行到第一个yield表达式,准备好作为生动的协程利用。
再看一个两个值得协程
next(my_coro2) # 向前实行到第一个yield 处 打印 “-> coroutine started: a = 14” # 并且产生值 14 (yield a 实行 等待为b赋值) print(inspect.getgeneratorstate(my_coro2)) # 这里inspect.getgeneratorstate(my_coro2) 得到结果为 GEN_SUSPENDED (协程处于停息状态) my_coro2.send(28) # 向前实行到第二个yield 处 打印 “-> Received: b = 28” # 并且产生值 a + b = 42(yield a + b 实行 得到结果42 等待为c赋值) print(inspect.getgeneratorstate(my_coro2)) # 这里inspect.getgeneratorstate(my_coro2) 得到结果为 GEN_SUSPENDED (协程处于停息状态) my_coro2.send(99) # 把数字99发送给停息协程,打算yield 表达式,得到99,然后把那个数赋值给c 打印 “-> Received: c = 99”def simple_coro2(a): print('-> coroutine started: a =', a) b = yield a print('-> Received: b =', b) c = yield a + b print('-> Received: c =', c)my_coro2 = simple_coro2(14)print(inspect.getgeneratorstate(my_coro2))# 这里inspect.getgeneratorstate(my_coro2) 得到结果为 GEN_CREATED (协程未启动)
# 协程终止,抛出StopIteration
运行上述代码,输出结果如下
GEN_SUSPENDED-> Received: b = 28 -> Received: c = 99GEN_CREATED-> coroutine started: a = 14
Traceback (most recent call last): File \"大众/Users/gs/coroutine.py\"大众, line 37, in <module> my_coro2.send(99)StopIteration
simple_coro2 协程的实行过程分为3个阶段,如下图所示
调用next(my_coro2),打印第一个,然后实行yield a,产出数字14.
调用my_coro2.send(28),把28赋值给b,打印第二个,然后实行 yield a + b 产生数字42
调用my_coro2.send(99),把99赋值给c,然后打印第三个,协程终止。
利用装饰器预激协程
我们已经知道,协程如果不预激,不能利用send() 传入非None 数据。以是,调用my_coro.send(x)之前,一定要调用next(my_coro)。
为了简化,我们会利用装饰器预激协程。
def coroutinue(func): ''' 装饰器: 向前实行到第一个`yield`表达式,预激`func` :param func: func name :return: primer ''' @wraps(func) def primer(args, kwargs): # 把装饰器天生器函数更换成这里的primer函数;调用primer函数时,返回预激后的天生器。 gen = func(args, kwargs) # 调用被被装饰函数,获取天生器工具 next(gen) # 预激天生器 return gen # 返复天生器 return primer # 利用方法如下 @coroutinue def simple_coro(a): a = yieldfrom functools import wraps
simple_coro(12) # 已经预激
终止协程和非常处理
协程中,为处理的非常会向上冒泡,通报给next函数或send方法的调用方,未处理的非常会导致协程终止。
看下边这个例子
from functools import wraps def coroutinue(func): ''' 装饰器: 向前实行到第一个`yield`表达式,预激`func` :param func: func name :return: primer ''' @wraps(func) def primer(args, kwargs): # 把装饰器天生器函数更换成这里的primer函数;调用primer函数时,返回预激后的天生器。 gen = func(args, kwargs) # 调用被被装饰函数,获取天生器工具 next(gen) # 预激天生器 return gen # 返复天生器 return primer @coroutinue#! -- coding: utf-8 --
def averager(): # 利用协程求均匀值 total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/countcoro_avg = averager()print(coro_avg.send(40))print(coro_avg.send(50))print(coro_avg.send('123')) # 由于发送的不是数字,导致内部有非常抛出。
实行上述代码结果如下
45.040.0
Traceback (most recent call last): File \"大众/Users/gs/coro_exception.py\"大众, line 37, in <module> print(coro_avg.send('123')) File \"大众/Users/gs/coro_exception.py\公众, line 30, in averager total += termTypeError: unsupported operand type(s) for +=: 'float' and 'str'
出错的缘故原由是发送给协程的'123'值不能加到total变量上。
出错后,如果再次调用 coro_avg.send(x) 方法 会抛出 StopIteration 非常。
由上边的例子我们可以知道,如果想让协程退出,可以发送给它一个特定的值。比如None和Ellipsis。(推举利用Ellipsis,由于我们不太利用这个值)
从Python2.5 开始,我们可以在天生器上调用两个方法,显式的把非常发给协程。
这两个方法是throw和close。
generator.throw(exc_type[, exc_value[, traceback]])
这个方法使天生器在停息的yield表达式处抛出指定的非常。如果天生器处理了抛出的非常,代码会向前实行到下一个yield表达式,而产出的值会成为调用throw方法得到的返回值。如果没有处理,则向上冒泡,直接抛出。
generator.close()
天生器在停息的yield表达式处抛出GeneratorExit非常。
如果天生器没有处理这个非常或者抛出了StopIteration非常,调用方不会报错。如果收到GeneratorExit非常,天生器一定不能产出值,否则阐明器会抛出RuntimeError非常。
示例: 利用close和throw方法掌握协程。
class DemoException(Exception): pass @coroutinue def exc_handling(): print('-> coroutine started') while True: try: x = yield except DemoException: print(' DemoException handled. Conginuing...') else: # 如果没有非常显示吸收到的值 print('--> coroutine received: {!r}'.format(x)) raise RuntimeError('This line should never run.') # 这一行永久不会实行import inspect
exc_coro = exc_handling()exc_coro.send(11)exc_coro.send(12)exc_coro.send(13)exc_coro.close()print(inspect.getgeneratorstate(exc_coro))
raise RuntimeError('This line should never run.') 永久不会实行,由于只有未处理的非常才会终止循环,而一旦涌现未处理的非常,协程会立即终止。
实行上述代码得到结果为:
--> coroutine received: 12 --> coroutine received: 13-> coroutine started--> coroutine received: 11
GEN_CLOSED # 协程终止
上述代码,如果传入DemoException,协程不会中止,由于做了非常处理。
print(inspect.getgeneratorstate(exc_coro))exc_coro.close()print(inspect.getgeneratorstate(exc_coro)) ## output-> coroutine started--> coroutine received: 11 --> coroutine received: 12 --> coroutine received: 13exc_coro = exc_handling()exc_coro.send(11)exc_coro.send(12)exc_coro.send(13)exc_coro.throw(DemoException) # 协程不会中止,但是如果传入的是未处理的非常,协程会终止
DemoException handled. Conginuing...GEN_SUSPENDEDGEN_CLOSED
如果不管协程如何结束都想做些处理事情,要把协程定义体重的干系代码放入try/finally块中。
def exc_handling(): print('-> coroutine started') try: while True: try: x = yield except DemoException: print(' DemoException handled. Conginuing...') else: # 如果没有非常显示吸收到的值 print('--> coroutine received: {!r}'.format(x))@coroutinue
finally: print('-> coroutine ending')
上述部分先容了:
天生器作为协程利用时的行为和状态
利用装饰器预激协程
调用方如何利用天生器工具的 .throw(...)和.close() 方法掌握协程
下一部分将先容:
协程终止时如何返回值
yield新句法的用场和语义
原文出处:goodspeed / 四月