生成器

Posted perfey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生成器相关的知识,希望对你有一定的参考价值。

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())   #(0,1,2,3)
# n = 10:执行g=(add(n,i) for i in (add(n,i) for i in test())) )  (5,6,7,8)
# n = 5:执行g=(add(5,i) for i in (add(n,i) for i in (add(n,i) for i in test())) ))
#                                  (10,11,12,13)          (5,6,7,8)           #(0,1,2,3)
#如果将for循环下面得表达式换成g=[add(n,i) for i in g] 结果为[11, 12, 13, 14]

#如果将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]










 























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())   #(0,1,2,3)
# n = 10:执行g=(add(n,i) for i in (add(n,i) for i in test())) )  (5,6,7,8)
# n = 5:执行g=(add(5,i) for i in (add(n,i) for i in (add(n,i) for i in test())) ))
#                                  (10,11,12,13)          (5,6,7,8)           #(0,1,2,3)
#如果将for循环下面得表达式换成g=[add(n,i) for i in g] 结果为[11, 12, 13, 14]

#如果将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]










 

  

以上是关于生成器的主要内容,如果未能解决你的问题,请参考以下文章

Apollo Codegen 没有找到生成代码的操作或片段

前端开发工具vscode如何快速生成代码片段

前端开发工具vscode如何快速生成代码片段

vscode代码片段生成vue模板

VS Code配置snippets代码片段快速生成html模板,提高前端编写效率

vs 2010代码片段