yield from

引子:

  python3.3之后加入了yield from表达式,相比yield多出来一些功能

  1 、建立客户端和子生成器之间 一个双向的通道 客户端直接发送值给子生成器。子生成器产生的值直接返回给客户端

  2、处理子生成器返回的异常 而GeneratorExit除外,yield产生的异常,如果生成器本身不处理,那么会冒泡到调用方,且生成器产生的值,会放在异常的第一个元素中

 

代码:

  1 生成器变协程



"""
to yield 可以看成2个解释  分别是 产出和让步 对于python的生成器中的yield来说  这些都成立
yield item 这行代码会产出一个值 供next(..)的调用方  另外也会做出让步 暂停生成器,让调用方继续的工作
直到需要另外一个值时在调用next() 调用方会从生成器中拉去值

从语法上看 协程与生成器非常相似 都是定义体中包含了 yield 关键字  
但是不同的地方在于  
    1 协程的的yield关键字通常处于 表达式的右侧  datum=yield 可以产出值  ,也可以不产出值 
      如果yield 的后边没有表示式 那么产出的值就是None
      协cheng可能会从调用方接收数据,不过调用方是使用send(item) 的方法给协cheng传递数据的 而不是next
    2  yield  可以不接受或者传出数据,不管数据如果流动  yield 都是一种流程控制工具,使用它可以实现协作式多任务
       协cheng可以把控制器让步给中心调度程序,从而激活其他的协cheng
       
    其实可以从根本上上把yield 看做流程控制   

"""

"""
1 生成器如何变成协程
2 使用装饰器自动预激活携程
3 调用方如何使用生成器对象的.close() .thorw() 前者的作用是让生成器关闭。后者的作用是让调用者抛出异常,在生成器中处理
4 携程终止时如果返回值
5 yield from 新语句发的用途和语义 

"""
# 生成器如何变成携程

# def simple_corount():
#     print("start")
#     x=yield # 如果yield 后边没有表达式 那么隐式的返回None
#     print("END")
#
# my=simple_corount()
# print(my)
# print(next(my))  # 隐式的返回None  生成器启动之前 需要使用调用next 在yield 语句出暂停  所以一开始无法发送数据
# print("*"*120)
# my.send(55) #调用此方法之后  yield 会计算出55  现在携程会回复 知道下一个yield 被调用  或者生成器被调用 。这里调用后流程控制会流向后边的代码  然后运行下边的语句 会跑出stopitertion异常

"""
send 方法的参数会 成为 暂停的yield 表示式的值  所以仅当携程处于暂停状态时才能调用send 方法 例如my(55) 如果携程还没有被激活 就传入了非None的值 就会报错
当然激活生成器的时候 也可以使用next  也可以  使用 my.send(None) 这样的值 

所以 在得到生成器函数之后  需要先调用next 或者 send(None) 这样预激活生成器  让生成器向前执行到第一个  yield 表达式,准备激活携程 
"""




"""
携程在yield关键字出停留  赋值语句中   =右边的代码先执行,在send 之后 在给 = 左边的值赋值 

"""


def simple_core2(a):
    print("-->start")
    print("func in a --", a)
    b=yield a   # a是生成器的返回值  这是时候会停留在这里 等待send给b赋值的时候 在网下走
    print("func in b --",b)
    c=yield a+b
    print("func in c ---",c)

from inspect import getgeneratorstate as gen_status

"""
查看生成器状态  
    1 GEN_CREATED  等待开始执行
    2 GEN_RUNNING  执行中
    3 GEN_SUSPENDED 在yield出暂停
    4 GEN_CLOSE   关闭

"""
my2=simple_core2(12)
print(gen_status(my2))
print(next(my2))
print(gen_status(my2))
print(my2.send(33))
print(gen_status(my2))
my2.send(44)
print(gen_status(my2))

  2 使用装饰器 预激 生成器

# -*- coding: utf-8 -*-
"""
@author:yuan_x
@software:PyCharm
@file:yield_from_coroutine2.py
@time:2020/12/8 11:34 下午


"""

"""

使用 装饰器预激 生成器
"""
# from functools import wraps
# from inspect import getgeneratorstate as gen_status
# def coroutine(func):
#     @wraps(func)
#     def primier(*args,**kw):
#         gen=func(*args,**kw)
#         next(gen)
#         return gen
#     return primier
#
# @coroutine
# def avger():
#     total=0
#     count=0
#     avg=None
#     while 1:
#         item=yield avg
#         total+=item
#         count+=1
#         avg=total/count
#
# gen=avger()
# print(gen_status(gen))
# print(gen.send(10))
# for i in range(5):
#     print(gen.send(i))
#     if i==4:
#         """
#         会报错  主动发送某个标志  甚至可以gen.send(StopIteration) 主动引发异常  不过StopIteration 必须是类本身而非实例
#         """
#         print(gen.send("NIHA"))
# gen.close()
# print(gen_status(gen))
#



"""
携程的异常处理和关闭
携程中未处理的异常会向上冒泡  传递给next 或者 send方法的调用方 即触发携程的对象

客户端可以 调用close 和throw 显示的把异常发送给携程  

  gen.throw(exc_type[,exc_value[,traceback]])
    致使生成器 暂停在yield表示式出  跑出指定的异常  如果生成器处理了这个异常  
    那么代码会向前执行到下一个yeild表达式,而产出的值 会成为调用gen.throw 方法得到的返回值
    如果生成器没有处理跑出的异常  异常会向上冒泡 传递到 调用放的上下文中
    
    
  gen.close()  
     致使生成器在暂停的yield 表示式出跑出异常 GeneratorExit异常   如果生辰器没有处理这个异常 或者抛出了 StopItertion 异常  通常是值运行到结尾
    调用方不会报错,如果收到了GeneratorExit异常,生成器一定不能产出值,否则解释器 会跑出RuntimeError的异常  生成器熬出的其他异常会向上冒泡 传递给调用方
"""

class DemoException(Exception):
    """
    生成器异常测试

    """

def demo_exc_handling():
    print("start")
    while 1:
        try:
            x=yield
        except DemoException:
            print("111demoexc2222")
        else:
            print("in demoex {!r}".format(x))
    # 这一句并不会执行 因为成功的时候走try  异常走except  走完try 走else  如果有异常或者其他未知异常 则携程直接终止
    raise RuntimeError("this lin should never run")


if __name__ == '__main__':
    exc_core=demo_exc_handling()
    next(exc_core)
    exc_core.send(11)
    exc_core.send(22)
    print("*"*120)
    exc_core.send((2,3,4,))

    # 主动传入携程能处理的异常类型 让携程处理
    # exc_core.throw(DemoException)

    # 主动传入携程不能处理的异常类型  携程无法处理 逐渐冒泡 抛出异常 结束携程
    print(">"*120)
    exc_core.throw(ZeroDivisionError)

  3 让生成器返回值

# -*- coding: utf-8 -*-
"""
@author:yuan_x
@software:PyCharm
@file:yield_from_coroutine3.py
@time:2020/12/10 11:58 下午


"""
"""
让生成器返回值

"""

from collections import namedtuple

ret=namedtuple("ret","count avg")
def avg():
    total=0
    count=0
    avg=None
    while 1:
        item=yield
        if item is None:
            break
        total+=item
        count+=1
        avg=total/count
    return ret(count,avg)

if __name__ == '__main__':
    gen=avg()
    next(gen)
    gen.send(10)
    gen.send(11)
    gen.send(12)
    # break 会跳出直接走return  打断携程 但是携程返回的值 会偷偷的传递给调用方 赋值给StopIteration异常对象的value
    #gen.send(None)
    """
    捕获异常中 携程的返回值
     yield from 结构会在内部自动捕获StopIteration 异常,这种处理的机制和for循环中的一样 循环机制使用用户已于理解的方式处理异常 
     对于yield from结构来说 解释器不仅会捕获 StopIteration异常,还会把value的属性值变成yield from表达式的值  
    """
    try:
        gen.send(None)
    except StopIteration as exc:
        rets=exc.value
        print(rets)

  4 yield from 与 yield


# -*- coding: utf-8 -*-
"""
@author:yuan_x
@software:PyCharm
@file:yield_from_coroutine4.py
@time:2020/12/11 10:46 下午


"""

"""

yield from 作用比yield 多很多 因此人么你认为继续使用那个关键字多少会引起误解 在其他语言中 类似的结构使用await
关键字,这个名称比较好 因为它传达了比较重要的一点 在生成器gen中 使用yield from subgen()时候,subgen 会获取控制权
把产出的值传给gen的调用方,即调用房可以直接控制subgen 于此同时 gen会阻塞 等待subgen终止

"""

# def gen():
# for c in "ab":
# yield c
# for i in range(1,3):
# yield i
#
# print(list(gen()))
#
# # 可以使用yield from方法进行改写
# def gen1():
# yield from 'ab'
# yield from range(1,3)
#
# print(list(gen1()))
#
# def gen2(*args):
# for it in args:
# yield from it
#
# s="abc"
# t=tuple(range(1,4))
# print(list(gen2(s,t)))


"""
yield from x 表达式 对x对象所做的第一件事情就是调用 iter(x) 从中获取迭代器因此x可以是任何可迭代对象
yield from 主要功能不仅仅是替代产出值嵌套for循环,yield from 的本质无法通过简单的可迭代对象说明 可以按照嵌套的生成器
其本质就是 把职责委托给子生成器的句法 yield form的主要功能是打开双向通道,把嘴外层的调用方与最内层的子语句连接起来
这样二者就可以直接发送和产出值 还可以直接传入异常 而不用再位于中间的携程中添加大量处理异常的样板代码 有了这个结构 携程可以通过以前不可能的方式委托职责

"""

"""
以下代码逻辑 委派生成器在yield from 表达式出暂停时 调用方可以直接把数据发送子生辰器 子生成器再把生成的值发送给调用方
子生成器返回完成之后 解释器会跑出 StopIteration异常 并把返回值附加到异常对象上 此时委派生辰器恢复
注意 yield 和 yield from 左边 yield 左边是接受send 的值 yield from 则是赋值



任何 yield from 链条都必须由客户端驱动 在最外层委派生成器 上调用next 函数 或者send 函数 可以隐式调用 比如 for循环


"""

"""
一下代码 运行逻辑
1 外层for循环每次迭代都会新建一个grouper 实例 赋值给group变量 ,group是委派生成器
2 调用next(group) 预激活委派生成器 grouper 此时进入while 1 循环 ,调用子生成器avger之后又 在yield from 表达式处暂停
3 内层for循环调用group.send() 直接把值传递给 子生成器avger 同时当前的grouper实例 在yield from处暂停
4 内层循环结束后 group 实例依然在yield from 表达式处暂停 因此grouper函数定义体中的为result[key] 赋值的语句还没有执行
5 如果外层for循环的末尾没有group.send(None) 那么avger子生成器永远不会终止 委派生成器group永远不会再次激活 因此永远不会为result[key]赋值
6 外层for循环重新迭代时会新建立一个grouper实例 然后绑定到group变量上 前一个grouper实例(以及它创建的尚未终止的avger子生成器实例)被gc回收

以下代码 证明一点 如果子生成器 不终止 委派生成器会在yield from 表达式永远暂停 如果是这样 程序不会向前执行 因为yield from 把控制权转交给客户端代码
显然有任务无法完成

所以基本的流程是
1 main函数中 的外层for 循环
"""
from collections import namedtuple
from inspect import getgeneratorstate as gen_status

import time
suna=0
ret=namedtuple("result","count avger")
def avg():
total=0
count=0
avger=None
while 1:
item=yield
if item is None:
break
total+=item
count+=1
avger=total/count
return ret(count,avger)


def grouper(result,key):
"""
在博客上看到一篇文章 引发了我一些思考 说 while 循环有什么用处
假如注释掉while 循环
客户端send(None)之后会引发子生成器跳出循环 子生成器的return 会返回值之后会引发StopIteration异常
但是这个异常 会被yield from 捕获且处理掉 这里总结下yield from 的2点主要作用
1 建立客户端和子生成器之间 一个双向的通道 客户端直接发送值给子生成器。子生成器产生的值直接返回给客户端
2 处理子生成器返回的异常 而GeneratorExit除外
最初的疑惑来源于 注释掉while 循环后 main函数的外层for循环为什么停止且爆出了StopIteration异常
最初看到的信息是 for循环会隐式的处理StopIteration异常,但是这个为什么没有
后来考虑了一下 因为grouper 本质上也是一个迭代器 result[key]=yield from avg()给result[key]之后就已经完成
但是子生成器返回的值已经在StopIteration被委托生成器捕获,并且进行了赋值随后yield from抛出StopIteration异常,进而造成程序的中断
如果加了while 循环或者 yield 那么会依然卡在yield from 或者 yield的后面 委托生成器并没有失效 也就不会跑出StopIteration异常 那么就不会诱发异常 这时候main函数是可以拿到子生成器返回的值的
          同理 result[key]=yield from 存在多行的情况有,程序也不会停止,这是因为委托生成器没有失效的情况下 main函数的外层for循环起了作用 grouper又被重新实例化 至于旧的完成了任务就被gc回收了"""





"""

while 1:
# 只要不接受到None 那么子生成器就不会结束 会卡在子生成器的yield后边


result[key]=yield from avg()

# result[key] = yield from avg()
# result[key] = yield from avg()


# result[key] = yield from avg()

#yield

def main(data):
result={}
A = 0
for k,v in data.items():

group=grouper(result,k)
next(group)
print('---1--->',gen_status(group))
for vv in v:
A+=1
print(group.send(vv))
b=group.send(None)
print("----", b, A)
# print("---====",group.send(None))
print('---2--->',gen_status(group))

report(result)

def report(results):
for k,r in sorted(results.items()):
a,b=k.split(';')
# print(results)
print("{:2} {:5} averagomg {:.2f} {}".format(r.count,a,r.avger,b))


data={"girls;kg":
[40.9,38.5,39.8,41.9,41.7],
"girls;m":
[1.2,1.3,1.4,1.5,1.6],
"boy;kg":
[40.0,49.8,44.0,43.4,44.4],
"boy;m":
[1.6,1.7,1.8,1.65,1.75]}

"""
还有一句话迷惑了我 就是for循环会自动处理 StopIteration 异常
对于可迭代对象确实是这样 但是对于生成器对象不是这样的
"""

# def func():
# yield 1
# yield 2
#
#
# a=func()
# #
# print(list(a))
# # print(list(a))
# # next(a)
#
# for i in range(1,4):
# print(next(a))

count_dict={1:"a",2:"b"}

# for i in count_dict:
# print(i)
#
# for i in count_dict:
# print(i)

if __name__ == '__main__':
main(data)
# print((list(test_query("abc",(1,2,3)))))
# bb=(1,2,3)
# print(lens(bb))
 

 

  

  

  

posted @ 2020-12-13 11:35  Yuan_x  阅读(174)  评论(0编辑  收藏  举报