参悟yield 和yield from (加精)
很久没有复习协程知识了,翻看文档感觉像个小学生一样,参悟了几个小时总算是透了,大佬们莫要见笑。
yield用法:
next(itr)相当于 itr.send(None),两种方式都会触发生成器运行
下面 代码 # 1处 运行至 yield index 的第一次断点 , #2 对jump进行赋值 并运行至第二个断点处 ......
def jumping_range(N): index = 0 while index < N: # 通过send()发送的信息将赋值给jump jump = yield index # print("jump", jump) if jump is None: jump = 1 index += jump if __name__ == '__main__': itr = jumping_range(5) print(next(itr)) # 1 print(itr.send(2)) # 2 print(next(itr)) # 3 print(itr.send(-1)) #4
如果想要完成所有的赋值取值, 最后一次next()会触发 StopIteration的错误 可以加try捕捉
例如:
if __name__ == '__main__': itr = jumping_range(5) print(next(itr)) print(itr.send(2)) print(next(itr)) print(itr.send(-1)) print(next(itr)) print(next(itr)) try: print(next(itr)) except StopIteration as e: print(e.value)
yield from的用法
不多说直接代码比较 ,下面一个例子
用yield实现
# 字符串 astr='ABC' # 列表 alist=[1,2,3] # 字典 adict={"name":"wangbm","age":18} # 生成器 agen=(i for i in range(4,8)) def gen(*args, **kw): print(args) for item in args: for i in item: yield i new_list=gen(astr, alist, adict, agen) print(next(new_list)) #print(type(new_list)) #['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
使用yield from
# 字符串 astr='ABC' # 列表 alist=[1,2,3] # 字典 adict={"name":"wangbm","age":18} # 生成器 agen=(i for i in range(4,8)) def gen(*args, **kw): for item in args: yield from item new_list=gen(astr, alist, adict, agen) print(list(new_list)) # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
由上面两种方式对比,可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。
yield from 的深层次应用:
首先得知道一个概念:
1、调用方:调用委派生成器的客户端(调用方)代码 2、委托生成器:包含yield from表达式的生成器函数 3、子生成器:yield from后面加的生成器函数
# 子生成器 def average_gen(): total = 0 count = 0 average = 0 while True: new_num = yield average count += 1 total += new_num average = total/count # 委托生成器 def proxy_gen(): while True: yield from average_gen() # 调用方 def main(): calc_average = proxy_gen() next(calc_average) # 预激下生成器 print(calc_average.send(10)) # 打印:10.0 print(calc_average.send(20)) # 打印:15.0 print(calc_average.send(30)) # 打印:20.0 if __name__ == '__main__': main()
委托生成器的作用: 在调用方和子生成器之间行成双向通道
双向通道的含义:调用方可以通过send()
直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。
注意:
你可能会经常看到有些代码,还可以在yield from
前面看到可以赋值。这是什么用法?
你可能会以为,子生成器yield回来的值,被委托生成器给拦截了。你可以亲自写个demo运行试验一下,并不是你想的那样。
因为我们之前说了,委托生成器,只起一个桥梁作用,它建立的是一个双向通道
,它并没有权利也没有办法,对子生成器yield回来的内容做拦截。
例子:
# 子生成器 def average_gen(): total = 0 count = 0 average = 0 while True: new_num = yield average if new_num is None: break count += 1 total += new_num average = total/count # 每一次return,都意味着当前协程结束。 return total,count,average # 委托生成器 def proxy_gen(): while True: # 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。 total, count, average = yield from average_gen() print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average)) # 调用方 def main(): calc_average = proxy_gen() next(calc_average) # 预激协程 print(calc_average.send(10)) # 打印:10.0 print(calc_average.send(20)) # 打印:15.0 print(calc_average.send(30)) # 打印:20.0 calc_average.send(None) # 结束协程 # 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程 if __name__ == '__main__': main()
结果:
10.0
15.0
20.0
计算完毕!!
总共传入 3 个数值, 总和:60,平均数:20.0