参悟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

 

浅层次谈论yield from的作用,它可以帮我们处理异常

上一篇:COCO数据集上的实验


下一篇:在Python中计算算术平均值(一种平均值)