python中的yield和yield from
学到了一点,就写一点吧:
本文不区分生成器和协程,且把生成器直接当做协程,对于from inspect import iscoroutine中的async协程类型一无所知,
开始:
yield: 流程控制工具: 可以把控制器让步给中心调度程序,从而激活其他协程.(简单说就是实现线程中任务切换)
协程四个状态:
1.'GEN_CREATED': 等待开始执行
2.'GEN_RUNNING': 解释器正执行
3.'GEN_SUSPENDED': 在yield表达式暂停 suspended: /sə'spendid/ 暂停的
4.'GEN_CLOSED': 执行结束
协程主要解决python中的高并发问题,是python无法充分利用多核的替代方案.(加粗的部分是我自己说的,有待考证)
以下通过几个案例来浅入浅出的来简单了解yield和yield from.
第一个例子: 简单使用yield
1 def func(): 2 print('start...') 3 x = yield 4 print('end, recived value: ', x) 5 return x 6 7 8 a = func() 9 print(getgeneratorstate(a)) # GEN_CREATED 状态1 10 print('***', next(a)) # *** None 用next预激活,此步运行了状态2,进入状态3,yield右侧没有表达式,默认返回None 11 print(getgeneratorstate(a)) # GEN_SUSPENDED 状态3,在x = yield暂停(执行了右边,还未给x赋值) 12 try: 13 a.send(666) # 给yield发送数据, 执行x = 666,打印: end, recived value: 666 并报错StopIteration 14 # 此时协程继续向下执行,知道遇到下一个yield,或终止(StopIteration) 15 except StopIteration as s: # 捕获异常,并获取返回值 16 print(s, s.value) # 666 666 17 print(getgeneratorstate(a)) # GEN_CLOSED 18 19 # 第一个next(a): 预激活协程, 让协程向前运行到第一个yield, 准备好作为活跃的协程使用.
第二个例子: 继续简单使用yield
1 def averager(): 2 total = 0.0 3 count = 0 4 average = None 5 while True: 6 term = yield average # yield分左右,右边使用用局部作用域的值(average=None),左边若客户端有send值过来,则取send的值,没有,则默认为None(term为None) 7 # 此时yield会将右侧的average值生产出去,语句为: a = next(generator),此时a = average = None(预激活时) 8 total += term 9 count += 1 10 average = total / count # 无限循环进行均值的计算,客户端可以无限次send数据 11 12 13 # 上面的代码是未完成的生成器代码(看作DEMO吧), 其实现的效果为: 只要客户端一直向协程发送数据(send(data)), 协程就会一直计算. 14 a = averager() 15 b = next(a) 16 print(b) # None 17 print(a.send(10)) # 10.0 18 print(a.send(20)) # 15.0 19 print(a.send(30)) # 20.0
第三个例子: 装饰器自动预激活(懒人专属)
1 # 第三个例子: 用装饰器预激活协程 2 from functools import wraps 3 4 5 def coroutine_prime(func): 6 @wraps(func) # wraps包装: 被包装函数会保留自己的属性 7 def primer(*args, **kwargs): 8 gen = func(*args, **kwargs) 9 next(gen) # 在装饰器中实现预激活功能 10 return gen 11 12 return primer 13 14 15 @coroutine_prime # 使用装饰器 16 def averager(): 17 total = 0.0 18 count = 0 19 average = None 20 while True: 21 term = yield average 22 total += term 23 count += 1 24 average = total / count 25 26 27 a = averager() # 此时已经预激活生成器,不用再调用next()函数 28 print(a.send(10)) # 10.0 29 print(a.send(20)) # 15.0 30 print(a.send(30)) # 20.0
第四个例子: yield转变为yield from的简单机制
1 from inspect import getgeneratorstate 2 3 4 class DemoException(Exception): 5 """异常示例""" 6 7 8 @coroutine_prime 9 def demo_func(): 10 print('start...') 11 try: 12 while True: 13 try: 14 x = yield 666 15 except DemoException: 16 print("It's DemoException.Go on...") 17 else: 18 print('received: ', x) 19 finally: 20 print("The End!.") 21 22 23 a = demo_func() # 实例化,预激活 打印: start... 24 a.send(10) # received: 10 25 a.send(20) # received: 20 26 print(a.throw(DemoException)) # It's DemoException.Go on... 666 throw返回值为下一个yield右侧的值 27 # print(a.throw(ZeroDivisionError)) # The End!. 传入未处理异常,直接整个程序向上冒泡至结束 28 print(getgeneratorstate(a)) # GEN_SUSPENDED 29 a.close() # The End! 30 print(getgeneratorstate(a)) # GEN_CLOSED
第五个例子: 简单使用yield from
其实在上面的例子一里面已经有协程返回值的影子了:
...
try:
a.send(666) # 给yield发送数据, 执行x = 666,打印: end, recived value: 666 并报错StopIteration
# 此时协程继续向下执行,知道遇到下一个yield,或终止(StopIteration)
except StopIteration as s: # 捕获异常,并获取返回值
print(s, s.value) # 666 666
想获取整个协程的返回值,必然需要等到协程终止,而判断协程终止必然会有StopIteration情况出现;
而yield from则集中处理了这些异常,并且实现返回值的功能.(这也是我说例子4是yield转变为yield from的简单机制的原因)
yield from 可以简单理解为: for x in iterable: yield x;即生成器嵌套.
但是yield from 的功能不仅如此.
其主要功能为:
打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,
还可以直接传入异常,而不用在协程中添加大量处理异常的代码.
1 from collections import namedtuple 2 3 Result = namedtuple('Result', 'count average') 4 5 6 # 子生成器: 原始逻辑处理 7 def averager(): 8 total = 0.0 9 count = 0 10 average = None 11 while True: 12 term = yield # 位置3 13 if term is None: # 位置6 14 break 15 total += term 16 count += 1 17 average = total / count 18 return Result(count, average) 19 20 21 # 委派生成器: yield from: 中间管道,连接子生成器和客户端,让双方数据直接对接 22 def grouper(results, key): 23 while True: # 位置7 24 results[key] = yield from averager() # 位置2 25 26 27 def report(results): 28 # 格式化打印结果 29 for key, result in sorted(results.items()): 30 group, unit = key.split(";") 31 print('{:2}{:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit)) 32 33 34 # 客户端调用,调用方,向子生成器输送数据,运行逻辑 35 def main(data): 36 results = {} 37 for key, values in data.items(): # 位置8 38 group = grouper(results, key) # 位置0 39 next(group) # 位置1 40 for value in values: # 位置4 41 group.send(value) 42 group.send(None) # 位置5 43 44 # print(results) 45 report(results) 46 47 48 data = { 49 'girls;kg': 50 [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5], 51 'girls;m': 52 [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43], 53 'boys;kg': 54 [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3], 55 'boys;m': 56 [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46], 57 } 58 59 if __name__ == '__main__': 60 main(data)
代码详解:(看的比较累的话可以先自己多运行下代码)
1.main函数处开始调用,到位置1处调用grouper,并执行next()进行预激活;(这里表明: yield from 和 自动预激活装饰器不兼容)
2.进入grouper()函数,进行while True循环,执行到yield from处(位置2),然后到该处暂停,开始执行averager()函数;(委派生成器开始阻塞,只作为调用通道)
3.进入averager()函数,一直运行到位置3处,term = yield,暂停在"="右侧,开始执行yield表达式,此时yield右侧为空,返回None给客户端(main);
4.回到1处(None值接不接收无所谓),进入位置4处的for循环,开始遍历values,并将value的值send到位置3处;(此时子生成器和客户端已经实现数据对接)
5.此时运行位置3处"="的左侧,即term = value,term拿到客户端发送过来的值,然后如上进行循环计算;
6.直到位置4处的循环结束,进入位置5,客户端发送给term一个None的值,子生成器进入位置6,中断循环,并return具名元组Result到位置2;
7.此时运行位置2处"="的左侧,即results[key] = Result(count, average),赋值完毕后,到位置7处,开始下一个循环,又开始调用averager();
8.直到位置8处的for循环完毕,将results字典传入report函数打印出结果,整个流程结束;
9.至此,关于客户端(调用方,main函数)通过委派生成器(yield form, grouper函数)和子生成器(averager函数)完成数据对接完整过程完毕.
上面的简单案例是一个委派生成器(yield from)和一个子生成器连接在一起,而yield from往往和asynicio模块一起做异步编程,虽然我不了解,但是肯定很牛X,学一点点先打个基础,为以后asynicio模块的学习做个准备吧.
以上,均为读"流畅的python"第16章'协程'后自己总结,内容必定不咋滴,待学有所成后再来自行校正.
End!

浙公网安备 33010602011771号