最近找到一本python好书《流畅的python》,是到如今为止看到的对python高级特性讲述最详细的一本。
看了协程一章,作个读书笔记,加深印象。python
协程的底层架构是在pep342 中定义,并在python2.5 实现的。多线程
python2.5 中,yield关键字能够在表达式中使用,并且生成器API中增长了 .send(value)方法。生成器可使用.send(...)方法发送数据,发送的数据会成为生成器函数中yield表达式的值。架构
协程是指一个过程,这个过程与调用方协做,产出有调用方提供的值。所以,生成器能够做为协程使用。函数
除了 .send(...)方法,pep342 和添加了 .throw(...)(让调用方抛出异常,在生成器中处理)和.close()(终止生成器)方法。ui
python3.3后,pep380对生成器函数作了两处改动:spa
生成器能够返回一个值;之前,若是生成器中给return语句提供值,会抛出SyntaxError异常。线程
引入yield from 语法,使用它能够把复杂的生成器重构成小型的嵌套生成器,省去以前把生成器的工做委托给子生成器所需的大量模板代码。code
首先说明一下,协程有四个状态,可使用inspect.getgeneratorstate(...)函数肯定:orm
GEN_CREATED # 等待开始执行协程
GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)
GEN_SUSPENDED # 在yield表达式处暂停
GEN_CLOSED # 执行结束
#! -*- coding: utf-8 -*- import inspect # 协程使用生成器函数定义:定义体中有yield关键字。 def simple_coroutine(): print('-> coroutine started') # yield 在表达式中使用;若是协程只须要从客户那里接收数据,yield关键字右边不须要加表达式(yield默认返回None) x = yield print('-> coroutine received:', x) my_coro = simple_coroutine() my_coro # 和建立生成器的方式同样,调用函数获得生成器对象。 # 协程处于 GEN_CREATED (等待开始状态) print(inspect.getgeneratorstate(my_coro)) 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表达式,或者终止。 my_coro.send(42) print(inspect.getgeneratorstate(my_coro))
运行上述代码,输出结果以下
GEN_CREATED -> coroutine started GEN_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表达式,准备好做为活跃的协程使用。
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 (协程未启动) 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” # 协程终止,抛出StopIteration
运行上述代码,输出结果以下
GEN_CREATED -> coroutine started: a = 14 GEN_SUSPENDED -> Received: b = 28 -> Received: c = 99 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)。
为了简化,咱们会使用装饰器预激协程。
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 def simple_coro(a): a = yield simple_coro(12) # 已经预激
协程中,为处理的异常会向上冒泡,传递给next函数或send方法的调用方,未处理的异常会致使协程终止。
看下边这个例子
#! -*- coding: utf-8 -*- 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 def averager(): # 使用协程求平均值 total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count coro_avg = averager() print(coro_avg.send(40)) print(coro_avg.send(50)) print(coro_avg.send('123')) # 因为发送的不是数字,致使内部有异常抛出。
执行上述代码结果以下
40.0 45.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 += term TypeError: 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异常。
import inspect 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.') # 这一行永远不会执行 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 started --> coroutine received: 11 --> coroutine received: 12 --> coroutine received: 13 GEN_CLOSED # 协程终止
上述代码,若是传入DemoException,协程不会停止,由于作了异常处理。
exc_coro = exc_handling() exc_coro.send(11) exc_coro.send(12) exc_coro.send(13) exc_coro.throw(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: 13 *** DemoException handled. Conginuing... GEN_SUSPENDED GEN_CLOSED
若是无论协程如何结束都想作些处理工做,要把协程定义体重的相关代码放入try/finally块中。
@coroutinue 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)) finally: print('-> coroutine ending')
上述部分介绍了:
生成器做为协程使用时的行为和状态
使用装饰器预激协程
调用方如何使用生成器对象的 .throw(...)和.close() 方法控制协程
下一部分将介绍:
协程终止时如何返回值
yield新句法的用途和语义
最后,感谢女友支持。
>欢迎关注 | >请我喝芬达 |
---|---|
![]() |
![]() |