ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

12-协程 yield\yield from

2022-02-21 19:34:22  阅读:173  来源: 互联网

标签:__ 12 协程 生成器 yield next print gen


协程:程序可以在多个函数间反复横跳运行,而不是传统的“从一而终”

def get_url(url):
    # do_something
    html = get_html(url) # 此处暂停,切换到另外一个函数去执行
    # parse_html
    urls = parse_url(html)
def get_url(url):
    # do_something
    html = get_html(url) # 此处暂停,切换到另外一个函数去执行
    # parse_html
    urls = parse_url(html)

传统函数调用过程 A->B->C
我们需要一个可以暂停的函数,并且可以在适当的时候恢复该函数的继续执行
出现了协程->有多个入口的函数,可以暂停的函数,(可以向暂停的地方传入信息)

def gen_func():
    yield 1
    yield 2
    yield 3
    return "bobby"
if __name__ == "__main__":
    gen = gen_func()
    # 生成器是实现迭代协议了的
    print(next(gen))
    print(next(gen))
    print(next(gen))
    print(next(gen)) # 最后迭代尽头,会返回bobby

生成器功能:生成器不仅可以产出值,还可以接收值,启动生成器有两种方式next和send
1、 yield 值 ————生成器内部产出值,给调用生成器的对象
调用生成器的对象,通过next()获取值,例如next(对象)
2、 yield from
调用生成器的对象,给生成器内部传递值

import asyncio
import socket

def gen_func():
    # 1、可以产出值,2、可以接收值
    html = yield "http://projectsedu.com"
    print(html)
    yield 2
    yield 3
if __name__ == "__main__":
    gen = gen_func()
    url = next(gen)  # 先执行yield "http://projectsedu.com",将 "http://projectsedu.com"返回给next(gen)的url
    print(url)
    # 将bobby传入到gen_func内部
    html = "bobby"
    # send方法可以进入生成器内部,同时还可以重启生成器执行到下一个yield
    # 在调用send发送非None值之前,必须先启动一次生成器,
        # 方式有两种1、直接调用next:next(对象)2、调用send传None值:对象.send(None)
    print(gen.send(html)) # 再执行将html的"bobby"传递给gen_func内部的html,但给gen.send(html)返回的是next(gen),第二个yield的值

生成器的关闭:生成器对象的close()方法,将生成器关闭后,就不能再继续对生成器进行操作

def gen_func():
    try:
        yield "http://projectsedu.com"
    # GeneratorExit是继承自BaseException,而不是Exception,异常的继承关系不同哦!
    except GeneratorExit:
        # 如果对GeneratorExit进行try-except的pass处理,则异常会在gen.close()主程序代码处抛出异常,而无法定位到具体位置
        pass
        # 如果对GeneratorExit进行抛出raise StopIteration异常,则异常会在next(gen)主程序代码处抛出异常,也无法定位到具体位置
    # 如果对yield "http://projectsedu.com"不做try-except异常捕获,则会在gen.close()主程序代码处抛出异常
    # 因此尽量不要对yield进行try_except处理异常
    yield 2
    yield 3
    return "helloworld"
if __name__ == "__main__":
    gen = gen_func()
    print(next(gen))
    # 将生成器关闭
    gen.close()
    next(gen) 
# 对象.throw(),抛出异常功能:
    # 抛出异常的位置,会定位到生成器内部的上一个yield位置,但如果对上一个yield位置进行try_except的pass异常处理,则生成器不会抛出异常
    # 有自动next功能: 在调用对象.throw()时,会返回try里的值,并进入到下一个yield
'''
def gen_func():
    try:
        yield 1
    except:
        pass
    try:
        yield 2
    except:
        pass
    try:
        yield 3
    except:
        pass
    yield 4
    yield 5
    return "helloworld"
if __name__ == "__main__":
    gen = gen_func()

    print(next(gen)) # 打印1
    # 抛出异常的位置,会定位到生成器内部的上一个yield位置,但如果对上一个yield位置进行try_except的pass异常处理,则生成器不会抛出异常
    # throw有自动next功能
    print(gen.throw(Exception,"download error")) # 打印2
    print(next(gen)) # 打印3
    gen.throw(Exception, "download error") # 抛出异常的位置,会定位到生成器内部的上一个yield位置,即yield 2的位置,这个位置没做异常处理
    print(next(gen)) # 打印5
    gen.throw(Exception, "download error")
'''

# python3.3新添了yield from语法
# chain可以将迭代的数据连接起来,进行for循环
'''
from itertools import chain
my_list = [1,2,3]
my_dict = {
    "bobby1":"http://projectsedu.com",
    "bobby2":"http://www.imooc.com",
}
# chain函数使用
for value in chain(my_list,my_dict,range(5,10)):
    print(value)  # 将三个iter对象,依次遍历(注:dict只遍历key值)
'''

# 通过yield复现chain函数
'''
def my_chain(*args,**kwargs):
    for my_iterable in args:
        for value in my_iterable:
            yield value
'''

# 通过yield from复现chain函数
    # yield from 后跟着iterable对象,可将iterable对象逐个yield出来
'''
def my_chain_1(*args,**kwargs):
    for my_iterable in args:
        yield from my_iterable
'''

# yield from与yield的区别
'''
def g1(iterable):
    yield range(10)
def g2(iterable):
    yield from range(10)

for value in g1(range(10)):
    print(value) # yield的是iterable对象,则打印出来的还是iterable对象
for value in g2(range(10)):
    print(value) # yield from的是iterable对象,则打印出来的是对象里的每一个值

def g1(gen):
    yield from gen
def main():
    g = g1()
    g.send(None)
'''

总结:
1、委托函数和子生成器都是生成器
2、调用方获取委托函数的生成器,并在启动后send传递数据
3、委托函数使用yield from 子生成器函数,可带参或不带参,以此建立调用方与子生成器的数据双向通道。yield from是中间通道(委托函数),yield是接收方(子生成器),send是传递方(调用方)
4、子生成器里的yield会接受调用方send出来的数据,并接着往下执行,注意,如果重复接受,用while True的话,要设置结束循环的条件
5、子生成器return回来的值,会返回给委托函数,所以,在委托函数里,如果子生成器有return,通道就设计为 res = yield from 子生成器函数,如果没有return,就设计为res = yield from

final_result = {}
# 子生成器
def sales_sum(pro_name):
    total = 0
    nums = []
    while True:
        x = yield  # 这里不产生值,而是接收值,只要在调用方执行send时,x就会接受到send过来的值,并且往下执行
        print(pro_name+"销量:",x)
        if not x:
            break
        total += x
        nums.append(x)
    # 子生成器return的数据,会返回到委托函数里
    return total,nums
# 委托函数
def middle(key):
    while True:
        # yield from后一般跟可迭代对象,这里传的是带参数的迭代器
        final_result[key] = yield from sales_sum(key)
        print(key+"销量统计完成!!")
# 调用方
def main():
    data_sets = {
        "bobby牌面膜":[1200,1500,3000],
        "bobby牌手机":[28,55,98,108],
        "bobby牌大衣":[280,560,778,70],
    }
    for key,data_set in data_sets.items():
        print("start key:",key)
        # m此刻是一个生成器
        m = middle(key)
        m.send(None)  # 预激middle协程,即启动,也可以用next(m),启动时是不传值的!或者只能传None
        for value in data_set:
            m.send(value) # 给协程传递每一组的值
        m.send(None)  # 结束子生成器的while True循环
    print("final_result:",final_result)

if __name__ == "__main__":
    main()

两个函数:
没有委托函数,两个函数交叉传递、接受数据,并运算

    def sales_sum():
        while True:
            # 可接收值
            sum = yield
            # 当结束sale_sum生成器时,会抛出StopIteration的异常
            if sum is not None:
                break
            print(sum)
            # 也可以不接收值
            '''
            yield 1
            print(1)
            '''
    def main():
        sales = [1,2,3,4,5,6]
        m = sales_sum() # m相当于协程
        m.send(None)  # 启动协程
        for i in sales:
            m.send(i)
        m.send(None)
    if __name__=="__main__":
        main()

三个函数:
子生成器——接收数据(相当于IO的接收方)
委托函数——中转处
调用方——发送数据(相当于IO的输出方)

案例:子生成器做数据处理(计算数据个数count、数据总和sum、数据平均值average)
    def sales_sum():
        sum = 0
        average = 0
        count = 0
        while True:
            value = yield
            if value is None: # 尽量不用if not value,因为如果传的是0,也会直接退出
                break
            sum += value
            count += 1
        average = sum/count
        return count,sum,average
    def middle():
        while True:
            result = yield from sales_sum()
            print(f"总共传了{result[0]}个值,总和是{result[1]},平均数是{result[2]}")
    def main():
        m = middle()
        next(m)
        for i in range(10):
            m.send(i)
        m.send(None)
    if __name__ == "__main__":
        main()

标签:__,12,协程,生成器,yield,next,print,gen
来源: https://blog.csdn.net/weixin_50348308/article/details/123053708

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有