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

  第二个例子: 继续简单使用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
第二个yield

  第三个例子: 装饰器自动预激活(懒人专属)

 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 to yield from

  第五个例子: 简单使用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)
yield from的简单案例

  代码详解:(看的比较累的话可以先自己多运行下代码)
    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!

 

posted @ 2020-07-06 07:42  葛小天  阅读(469)  评论(0)    收藏  举报