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) 
    

      

  • 相关阅读:
    Sum Root to Leaf Numbers 解答
    459. Repeated Substring Pattern
    71. Simplify Path
    89. Gray Code
    73. Set Matrix Zeroes
    297. Serialize and Deserialize Binary Tree
    449. Serialize and Deserialize BST
    451. Sort Characters By Frequency
    165. Compare Version Numbers
    447. Number of Boomerangs
  • 原文地址:https://www.cnblogs.com/perfey/p/10105605.html
Copyright © 2011-2022 走看看