导语:本文章记录了本人在学习Python基础之控制流程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。
本文重点:
1、掌握协程的概念与行为;
2、掌握协程中的预激,终止和异常处理;
3、深入理解yield from的本质作用。
一、协程介绍
1、协程概述
协程:指的是与调用方协作,产出由调用方提供的值。
语法结构:协程是定义体中包含yield关键字的函数,一般使用生成器函数定义。意义:协程中的yield关键字是一种控制流程工具。即不管数据如何流动,协程都会把控制权让步给中心调度程序,从而激活其他的协程实现协作式多任务。
2、协程的基本行为
协程包含四种状态:
- GEN_CREATED:等待开始执行。
- GEN_RUNNING:解释器正在执行。
- GEN_SUSPENDED:在yield表达式处暂停。
- GEN_CLOSED:执行结束。
可使用inspect.getgeneratorstate(...)查询协程所处的状态。
协程中重要的两个方法:
- .send(datum):调用方把数据提供给协程。
- next(coroutine):预激协程。
协程返回值:自Python3.3实现PEP 380以来对生成器函数做了两处改动,一处是生成器可以返回值。
3、实例1:协程初级使用——计算平均值
下面将利用协程计算用户传入若干数值的平均值。
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 表达式的值赋给左边的变量。
实例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异常。
二、协程的预激、终止与异常处理
1、预激协程
协程在使用前须预激,让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用。
预激的本质方法:
- next(coroutine):常见的标准方法。
同时首次发送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句法。
2、终止协程
协程中未处理的异常会向上冒泡,传给next函数或send方法的调用方。
因此,终止协程的本质在于向协程发送其无法处理的异常。下面介绍三种方法终止协程:
-
发送哨符值
。常用None和Ellipsis,甚至StopIteration类也可以发送。 -
generator.throw(exc_type[,exc_value[,traceback]])
令生成器在暂停的yield表达式处抛出指定的异常。若生成器处理了此异常,则生成器向前执行到下一个yield表达式,而产出的值会成为调用generator.throw得到的返回值。否则,异常会向上冒泡,传到调用方的上下文中。 -
generator.close()
令生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器不处理此异常,或者跑出来StopIteration,调用方不会报错。如果收到GeneratorExit异常,生成器一定不能产出值,否则解释器会抛出RuntimeError异常。
后两种方法是自Python2.5开始显式发送异常的两个方法,建议使用后两种方法来终止协程。
3、处理异常
在使用协程的过程中会产生一些需要处理的异常,此时可利用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')
三、yield from结构
1、yield from结构介绍
作用介绍:本质作用是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样两者可以直接发送和产出值,还可以直接传入异常。
替代产出值的嵌套for循环。
执行机制:(1)在生成器gen中使用yield from subgen()时,subgen会获得控制权,把产出的值传给gen的调用方,即调用方可以直接控制subgen。与此同时,gen会阻塞,等待subgen终止。
(2)yield from结构会在内部自动捕获StopIteration异常,还会把对应的value属性值变成yield from表达式的值。
2、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]
3、PEP380中总结的yield from的六点行为
- 子生成器产出的值都直接传给委派生成器的调用方。
- 使用send()方法发给委派生成器的值都直接传给子生成器。如果发送None会调用生成器的__next__()方法。如果发送的不是None,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。
- 生成器退出时,生成器中的return表达式会触发StopIteration(expr)异常抛出。
- yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。
- 传入委派生成器的异常,除了GeneratorExit之外都传给子生成器的throw()方法。如果调用throw()方法时抛出StopIteration异常,委派生成器恢复运行。StopIteration之外的异常会向上冒泡,传给委派生成器。
- 如果把 GeneratorExit 异常传入委派生成器, 或者在委派生成器上调用 close() 方法, 那么在子生成器上调用 close() 方法, 如果它有的话。 如果调用 close() 方法导致异常抛出, 那么异常会向上冒泡, 传给委派生成器; 否则, 委派生成器抛出GeneratorExit 异常。
四、协程与生成器异同
生成器用于生成供迭代的数据。
协程是数据的消费者,能完成协作式多任务活动。
协程与迭代无关。尽管在协程中会使用 yield 产出值, 但这与迭代无关。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。