zoukankan      html  css  js  c++  java
  • 生成器 yield和协程

    yield和协程
    推荐博客:https://blog.csdn.net/soonfly/article/details/78361819
    yield具有return的功能,只是yield是中断函数(更准确的说是生成器),等待下一个next()或send()再继续执行至下一个yield
    协程就是利用一个cpu运行一个线程,通过yield分时段执行多个任务,即当执行的任务遇到IO阻塞等待时,cpu就不执行这个任务转而顺序执行下一个任务
    def gen():
        value=0
        while True:
            receive=yield value
            if receive=='e':
                break
            value = 'got: %s' % receive
    
    g=gen()
    print(g.send(None))    
    print(g.send('hello'))
    print(g.send(123456))
    print(g.send('e'))
    
    其实receive=yield value包含了3个步骤: 
    1、向函数外抛出(返回)value 
    2、暂停(pause),等待next()或send()恢复 
    3、赋值receive=MockGetValue() 。 这个MockGetValue()是假想函数,用来接收send()发送进来的值
    
    1、通过g.send(None)或者next(g)启动生成器函数,并执行到第一个yield语句结束的位置
    	运行receive=yield value语句时,我们按照开始说的拆开来看,实际程序只执行了1,2两步,程序返回了value值,并暂停(pause),并没有执行第3步给receive赋值。因此yield value会输出初始值0
    2、通过g.send('hello'),会传入hello,从上次暂停的位置继续执行,那么就是运行第3步,赋值给receive。然后计算出value的值,并回到while头部,遇到yield value,程序再次执行了1,2两步,程序返回了value值,并暂停(pause)。此时yield value会输出”got: hello”,并等待send()激活
    3、通过g.send(123456),会重复第2步,最后输出结果为”got: 123456″。
    4、当我们g.send(‘e’)时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。
    
    示例2
    def generator():
        print(123)
        content = yield 1
        print('=======',content)
        print(456)
        yield2
    
    g = generator()
    ret = g.__next__()
    print('***',ret)
    ret = g.send('hello')   #send的效果和next一样
    print('***',ret)
    
    #send 获取下一个值的效果和next基本一致
    #只是在获取下一个值的时候,给上一yield的位置传递一个数据
    #使用send的注意事项
        # 第一次使用生成器的时候 是用next获取下一个值
        # 最后一个yield不能接受外部的值
    	
    	
    

      

    yield from
    
    def g1():     
         yield  range(5)
    def g2():
         yield  from range(5)
    
    it1 = g1()
    it2 = g2()
    for x in it1:
        print(x)
    
    for x in it2:
        print(x)
    输出结果:
    range(0, 5) 
    0 
    1 
    2 
    3 
    4
    
    这说明yield就是将range这个可迭代对象直接返回了。 
    而yield from解析了range对象,将其中每一个item返回了。 
    yield from iterable本质上等于for item in iterable: yield item的缩写版,对可迭代对象的元素逐一yield
    
    来看一下例子,假设我们已经编写好一个斐波那契数列函数
    def fab(max):
         n,a,b = 0,0,1
         while n < max:
              yield b
              # print b
              a, b = b, a + b
              n = n + 1
    f=fab(5)
    fab不是一个普通函数,而是一个生成器。因此fab(5)并没有执行函数,而是返回一个生成器对象(生成器一定是迭代器iterator,迭代器一定是可迭代对象iterable) 
    现在我们来看一下,假设要在fab()的基础上实现一个函数,调用起始都要记录日志(这里也给我一个启示,不使用装饰器,给生成器记录日志,给生成器添加装饰器也不能直接在装饰器中执行生成器函数,而是要使用for循环迭代执行)
    def f_wrapper(fun_iterable):
        print('start')
        for item  in fun_iterable:
            yield item
        print('end')
    wrap = f_wrapper(fab(5))
    for i in wrap:
        print(i,end=' ')
    #start
    #1 1 2 3 5 end
    
    给生成器添加装饰器
    def init(func):  #在调用被装饰生成器函数的时候首先用next激活生成器
        def inner(*args,**kwargs):
            g = func(*args,**kwargs)
            next(g)
            return g
        return inner
    
    @init
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            term = yield average
            total += term
            count += 1
            average = total/count
    
    
    g_avg = averager()
    # next(g_avg)   在装饰器中执行了next方法
    print(g_avg.send(10))
    print(g_avg.send(30))
    print(g_avg.send(5))
    
    
    
    现在使用yield from代替for循环
    import logging
    def f_wrapper2(fun_iterable):
        print('start')
        yield from fun_iterable  #注意此处必须是一个可生成对象
        print('end')
    wrap = f_wrapper2(fab(5))
    for i in wrap:
        print(i,end=' ')
    
    ***yield from后面必须跟iterable对象(可以是生成器,迭代器)***
    
    
    asyncio.coroutine和yield from
    import asyncio,random
    @asyncio.coroutine
    def smart_fib(n):
        index = 0
        a = 0
        b = 1
        while index < n:
            sleep_secs = random.uniform(0, 0.2)
            yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
            print('Smart one think {} secs to get {}'.format(sleep_secs, b))
            a, b = b, a + b
            index += 1
    
    @asyncio.coroutine
    def stupid_fib(n):
        index = 0
        a = 0
        b = 1
        while index < n:
            sleep_secs = random.uniform(0, 0.4)
            yield from asyncio.sleep(sleep_secs) #通常yield from后都是接的耗时操作
            print('Stupid one think {} secs to get {}'.format(sleep_secs, b))
            a, b = b, a + b
            index += 1
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        tasks = [
            smart_fib(10),
            stupid_fib(10),
        ]
        loop.run_until_complete(asyncio.wait(tasks))
        print('All fib finished.')
        loop.close()
    yield from语法可以让我们方便地调用另一个generator。 
    本例中yield from后面接的asyncio.sleep()是一个coroutine协程(里面也用了yield from),所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。 
    asyncio是一个基于事件循环的实现异步I/O的模块。通过yield from,我们可以将协程asyncio.sleep的控制权交给事件循环,然后挂起当前协程;之后,由事件循环决定何时唤醒asyncio.sleep,接着向后执行代码。 
    协程之间的调度都是由事件循环决定。 
    

      

    async和await
    弄清楚了asyncio.coroutine和yield from之后,在Python3.5中引入的async和await就不难理解了:可以将他们理解成asyncio.coroutine/yield from的完美替身。当然,从Python设计的角度来说,async/await让协程表面上独立于生成器而存在,将细节都隐藏于asyncio模块之下,语法更清晰明了
    加入新的关键字 async ,可以将任何一个普通函数变成协程
    import time,asyncio,random
    async def mygen(alist):
        while len(alist) > 0:
            c = randint(0, len(alist)-1)
            print(alist.pop(c))
    a = ["aa","bb","cc"]
    c=mygen(a)
    print(c)
    输出:
    <coroutine object mygen at 0x02C6BED0>
    
    在上面程序中,我们在前面加上async,该函数就变成一个协程了。
    但是async对生成器是无效的。async无法将一个生成器转换成协程。 (因此我们上面的函数使用的是print,不是yield)
    
    要运行协程,要用事件循环 
    import time,asyncio,random
    async def mygen(alist):
        while len(alist) > 0:
            c = random.randint(0, len(alist)-1)
            print(alist.pop(c))
            await asyncio.sleep(1)
    strlist = ["ss","dd","gg"]
    intlist=[1,2,5,6]
    c1=mygen(strlist)
    c2=mygen(intlist)
    print(c1)
    if __name__ == '__main__':
            loop = asyncio.get_event_loop()
            tasks = [
                c1,
                c2
            ]
            loop.run_until_complete(asyncio.wait(tasks))
            print('All fib finished.')
            loop.close()
    

      

    面试题:
    def demo():
        for i in range(4):
            yield i
    
    g=demo()
    
    g1=(i for i in g)  #g1也是个生成器,但是直到调用list(g1)才运行g1里的语句,然后把调用到g的值,悉数给了list,此时g1作为生成器,取到头了,没值了
    g2=(i for i in g1)  #当list(g2)向g1要值得时候,g1没东西给它
    
    print(list(g1))
    print(list(g2))
    # [0, 1, 2, 3]   g1、g2均是生成器,通过for可以把生成器中的值取出来,当全部取出来后,继续取值就会为空了
    # []
    
    
    def add(n,i):
        return n+i
     
    def test():
        for i in range(4):
             yield i
     
    g=test()
    for n in [1,10]:
        g=(add(n,i) for i in g)   #先执行 for i in g ,再执行 add函数
     
    	print(list(g))
    
    # 生成器的特点是惰性机制,也就是说你找它要,它才会给你值	
    # for n in [1,10]:
    #     g=(add(n,i) for i in g)  #先执行 for i in g ,再执行 add函数
    # 可以看成是:
    # n = 1: 执行g=(add(n,i) for i in g)
    # n = 10:执行g=(add(n,i) for i in g)
    # 但是g作为生成式表达式,这里说的g是括号外的g,只是一个内存地址,调用不到
    # 所以此时 n = 10,然后继续往下执行 print(list(g))
    # 此时开始调用g,但是执行的是 n = 10:执行g=(add(n,i) for i in g)  同时括号里的g 应该替换为 当 n=1的时候,执行的g
    # 也就是 此时执行的是  n = 10: g=(add(10,i) for i in g=(add(10,i) for i in g))
    #注意此处 n的变化
    #然后就变为了  g=(add(10,i) for i in (10,11,12,13))
    #最后结果为 [20,21,22,23]
     
    #如果换成
    #for n in [1,10,5]:
    #    g=(add(n,i) for i in g)
    #可以看成:
    # n = 1: 执行g=(add(n,i) for i in test())   
    # n = 10:执行g=(add(n,i) for i in (add(n,i) for i in test())) )  
    # n = 5:执行g=(add(5,i) for i in (add(n,i) for i in (add(n,i) for i in test())) ))  里面的n全部为5
    #                                 
    #如果将for循环下面得表达式换成g=[add(n,i) for i in g] 结果为[11, 12, 13, 14]  此时g不再是生成器而是列表,所以在循环中就直接执行l
    # n = 1: 执行g=[add(n,i) for i in g]  g = [1,2,3,4]
    # n = 10:执行g=[add(n,i) for i in g]  g = [add(n+i) for i in [1,2,3,4]]  g=10,11,12,13
    
    #如果将for循环的表达式换成 for n in [1,3,10]  或者 for n in [1,11,10] 其结果为[30, 31, 32, 33]
    
    #如果是 for n in [1,3,6,7,10,11,100,10] 其结果为[80, 81, 82, 83]  即最后一位数 10 乘以他的索引 +1 就是8再分别加上0,1,2,3,所以最后结果是80,81,82,83,
    

     

    import os
    # 当前目录下有个test1文件夹,里面有t1.txt和t2.txt,这两个文件中有一行的内容包含python字符串
    
    def init(func):
        def wrapper(*args, **kwargs):
            g = func(*args, **kwargs)
            next(g)
            return g
    
        return wrapper
    
    
    @init
    def list_files(target):
        while 1:
            dir_to_search = yield  # 接收 g.send('test1') 发过来的test1
            for top_dir, dir, files in os.walk(dir_to_search):
                # top_dir正在遍历的这个文件夹的本身的地址 ;
                # dir是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录);
                # files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)
                print(files)   # ['t1', 't2']
                for file in files:
                    target.send(os.path.join(top_dir, file))  # target是list_files的参数,这里指的是opener生成器,向这个生成器发送文件的绝对路径
    
    
    @init
    def opener(target):
        while 1:
            file = yield  # 这里接收各个文件的绝对路径
            fn = open(file)
            print(fn)
            target.send((file, fn))  # 同样的使参数中的生成器发送文件路径和文件句柄
    
    
    @init
    def cat(target):
        while 1:
            file, fn = yield  # 接收文件路径和文件句柄
            for line in fn:  
                print(line)
                target.send((file, line))  # 向grep生成器发送文件路径和每行的内容
    
    
    @init
    def grep(pattern, target):
        while 1:
            file, line = yield  # 接收文件路径和每行内容
            if pattern in line: # 当pattern参数(这里的是'python')在一行的内容中时,将文件名发送给printter生成器
                target.send(file)
    
    
    @init  # 装饰器的作用是让生成器抛出一个值,
    def printer():
        while 1:
            file = yield  # 接收文件名
            if file:
                print(file) #打印文件名
    
    
    g = list_files(opener(cat(grep('python', printer()))))
    # 都运行在yield上
    
    g.send('test1')  # 这里的g是生成器list_files,
    

      

     

    #基于yield并发执行的协程
    import time
    def consumer():
        '''任务1:接收数据,处理数据'''
        while True:
            print('我是consumer生成器,我在接收数据')
            x=yield
    
    def producer():
        '''任务2:生产数据'''
        g=consumer()
        next(g)
        for i in range(100):
            g.send(i)  # 通过send() 或者next()控制生成器consumer是否运行,用这个特性来分时段执行多个任务
            # 我们可以在这个函数中管理多个生成器(对应多个任务),然后通过一些条件(比如说for i in range() 如果i对3取余,余数是0执行任务1,余数是1或2执行任务2),来模拟各个任务的执行时间的不同,来控制任务的执行
            print(f'我是producer,我给生成器发送数据,数据是{i}')
    
    start=time.time()
    #基于yield保存状态,实现两个任务直接来回切换,即并发的效果
    #PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
    producer()
    
    stop=time.time()
    print(stop-start) 
    

      

  • 相关阅读:
    感动于细节,记我的一个同学(君君)
    求职时,我问过的问题
    毕业留言,写给我的一个同学
    偶是一个如此善良的人
    温柔地对待仇人,就像对待情人一样!
    小改机箱,支持硬盘调头,让磁盘对拷更方便
    心中的女朋友:(标准)
    准备开始看以下书籍
    成都文化公园游后感
    ”中国的教育与计算机“读后感
  • 原文地址:https://www.cnblogs.com/perfey/p/10105605.html
Copyright © 2011-2022 走看看