导语:本文章记录了本人在学习Python基础之控制流程篇的重点知识及我的心得,打算入门Python的朋友们能够来一块儿学习并交流。
本文重点:python
一、掌握协程的概念与行为;
二、掌握协程中的预激,终止和异常处理;
三、深刻理解yield from的本质做用。
协程:指的是与调用方协做,产出由调用方提供的值。
语法结构:协程是定义体中包含yield关键字的函数,通常使用生成器函数定义。意义:协程中的yield关键字是一种控制流程工具。即无论数据如何流动,协程都会把控制权让步给中心调度程序,从而激活其余的协程实现协做式多任务。
架构
协程包含四种状态:async
可以使用inspect.getgeneratorstate(...)查询协程所处的状态。函数
协程中重要的两个方法:工具
协程返回值:自Python3.3实现PEP 380以来对生成器函数作了两处改动,一处是生成器能够返回值。
学习
下面将利用协程计算用户传入若干数值的平均值。ui
def average(): total=0.0 number=0 average=None while True: term=yield average total+=term number+=1 average=total/number print(average) process=average() next(process)#预激协程 process.send(5)#输出5 process.send(10)#输出7.5 process.send(15) #输出10.0
小结:协程执行首先须要预激,使之准备好而后让步控制权。具体地说,协程在yield关键字所在的位置暂停执行。在term=yield average这个 赋值语句中,右边的代码会在赋值以前执行。 在暂停结束后,从先前阻塞的那行代码开始,将yield 表达式的值赋给左边的变量。
spa
实例2:令协程返回值设计
from collections import namedtuple Result = namedtuple('result','average count') def average(): total = 0.0 number = 0 average = None while True: term = yield if term is None: break total += term number += 1 average=total/number return Result(average,number)
分析:当协程终止时,能够在return表达式中返回值。而且return表达式经过把值绑定到StopIteration的value属性上传给调用方返回值。事实上这也符合生成器的常规行为——耗尽时抛出StopIteration异常。code
协程在使用前须预激,让协程向前执行到第一个yield表达式,准备好做为活跃的协程使用。
预激的本质方法:
同时首次发送coroutine.send(None)也能够调用next(coroutine),实现相同功能,但缺少可读性。
基于本质方法,咱们衍生出自定义预激协程的装饰器的方法,避免忘记预激协程。
coroutine:预激协程的装饰器
from functools import wraps def coroutine(func): @wraps(func)#把func相关属性复制过来 def manage(*args,**kwargs): gen=func(*args,**kwargs)#获取生成器对象 next(gen)#预激协程 return gen#返回协程 return manage
只需将@coroutine语法糖加在生成器函数上,就能够经过构造生成器对象获取活跃的协程。
注意:
使用yield from调用协程时会自动预激,所以与@coroutine装饰器不兼容;
Python3.4标准库中的asyncio.coroutine装饰器不会预激协程,所以能兼容yield from句法。
协程中未处理的异常会向上冒泡,传给next函数或send方法的调用方。
所以,终止协程的本质在于向协程发送其没法处理的异常。下面介绍三种方法终止协程:
发送哨符值
。经常使用None和Ellipsis,甚至StopIteration类也能够发送。generator.throw(exc_type[,exc_value[,traceback]])
generator.close()
后两种方法是自Python2.5开始显式发送异常的两个方法,建议使用后两种方法来终止协程。
在使用协程的过程当中会产生一些须要处理的异常,此时可利用try/except处理。若是无论协程如何结束都要作一些清理工做,请使用try/finally处理。
实例1:使用try/finally在协程终止时执行操做
class DemoException(Exception): """为此次演示定义的异常类型。 """ def demo_finally(): print('-> coroutine started') try: while True: try: x = yield except DemoException: print('*** DemoException handled. Continuing...') else: print('-> coroutine received: {!r}'.format(x)) finally: print('-> coroutine ending')
做用介绍:本质做用是打开双向通道,把最外层的调用方与最内层的子生成器链接起来,这样二者能够直接发送和产出值,还能够直接传入异常。
替代产出值的嵌套for循环。
执行机制:(1)在生成器gen中使用yield from subgen()时,subgen会得到控制权,把产出的值传给gen的调用方,即调用方能够直接控制subgen。与此同时,gen会阻塞,等待subgen终止。
(2)yield from结构会在内部自动捕获StopIteration异常,还会把对应的value属性值变成yield from表达式的值。
实例1:对yield from架构双向通道本质的深刻理解
下面咱们结合实例深刻理解yield from结构。假设咱们须要利用yield from分别计算一个班级男女生身高和体重的平均值,并予以输出。采用“外部调用方+委派生成器+子生成器”的结构进行设计,结构示意图以下:
实例代码:
from collections import namedtuple Result=namedtuple('Result','average number') def subaverager():#子生成器经委派生成器处理外部数据,并将值返回给委派生成器。 total = 0.0 number = 0 average = None while True: term = yield if term is None:#外部调用方控制子生成器终止的关键语句。 break total += term number += 1 average=total/number return Result(average,number) def averager(results,key):#委托生成器架构双向通道。 while True:#避免StopIteration。当获得子生成器的返回值时,程序会执行到下一个yield。 results[key]=yield from subaverager() def main(grouper): results={} for key,group in grouper.items(): term = averager(results,key)#构建生成器对象。 next(term) for value in group: term.send(value) term.send(None)#外部调用方控制子生成器终止的语句。 print(results) result(results) def result(results):#格式化输出协程返回的处理数据。 for key,value in results.items(): gender,unit=key.split(';') print('{} {} averaging {:.2f} {}.'.format( value.number,gender,value.average,unit)) data = { 'girls;kg': [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5], 'girls;m': [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43], 'boys;kg': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3], 'boys;m': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46], } if __name__=='__main__': main(data)
思路扩展:上例展现的结构中仅有一个委派生成器和一个子生成器。事实上,这种调用关系能够扩展到更多的委托生成器上。即把多个委派生成器链接到一块儿。一个委派生成器调用另外一个子生成器,这个子生成器自己也是委派生成器。这种链式结构最终以一个只使用yield的简单生成器结束,或者任何的可迭代对象结束。
实例2:替代产出值的嵌套for循环
def gen(): for c in 'AB': yield c for i in range(1, 3): yield i print(list(gen()))#输出['A', 'B', 1, 2]
能够简化成:
def gen(): yield from 'AB' yield from range(1, 3) print(list(gen()))#输出['A', 'B', 1, 2]
生成器用于生成供迭代的数据。
协程是数据的消费者,能完成协做式多任务活动。
协程与迭代无关。尽管在协程中会使用 yield 产出值, 但这与迭代无关。