生成器

为什么需要生成器:

假如现在有一个需求,要打印从1-1亿的整形。如果采用普通的方式,直接调用range函数,那么程序肯定会崩溃,因为range(1,100000000)函数直接产生一个从1-1亿的列表,这个列表中的所有数据都是存放在内存中的,会导致内存爆满。这时候可以采用生成器来解决这个问题,生成器不会一次性把所有数据都加载到内存中,而是在循环的时候临时生成的,循环一次生成一个,所以在程序运行期间永远都只会生成一个数据,从而大大节省内存。

解决打印1-1亿的问题:
range函数配合圆括号可以产生一个生成器:

    # 普通的列表
    num_list = [x for x in range(1,100000000)]
    print(type(num_list))

    # 生成器
    num_gen = (x for x in range(1,100000000))
    print(type(num_gen))

生成器运行原理:

生成器可以通过函数产生。如果在一个函数中出现了yield表达式,那么这个函数将不再是一个普通的函数,而是一个生成器函数。yield一次返回一个结果,并且会冻结当前函数的状态。

用生成器解决1-1亿的问题:

  1. 使用圆括号的生成器:
    ret = (x for x in range(1,100000000))
    print(type(ret))
    
  2. 使用函数:
    def my_gen(start,end):
        index = start
        while index <= end:
            yield index
            index += 1
    
    # 生成器有两个身份:迭代器和可迭代的对象
    ret = my_gen(1,100000000)
    for x in ret:
        print(x)
    

生成器其实是迭代器也是可迭代对象:

因为生成器迭代器也是可迭代的对象,那么可以通过for循环进行遍历。并且因为它自身集成了迭代器可迭代对象两个身份,因此它能被遍历一次。

next方法:

next函数可以迭代生成器的返回值

生成器可以通过函数产生。如果在一个函数中出现了yield表达式,那么这个函数将不再是一个普通的函数,而是一个生成器函数。yield一次返回一个结果,并且会冻结当前函数的状态。以下是一个非常简单的生成器:

 def my_gen():
     yield 1
     yield 2
     yield 3

那么想要获取里面的值,可以通过以下方式获取:

    ret = my_gen()
    print(next(ret))
    print(next(ret))
    print(next(ret))

send方法:

  1. send方法和next方法类似,可以用来触发生成器的下一个yield,但是send不仅可以触发下一个yield,还可以发送数据过去,作为yield表达式的值。
  2. 如果用send方法执行刚刚开始的生成器,那么应该传Nonesend方法。不传或者传其他值都会报错。
def my_gen(start):
        while start < 10:
            temp = yield start
            print(temp)
            start += 1


    ret = my_gen(1)
    # next(ret)返回1
    # 以下结果会打印1
    print(next(ret))
    # send("Hello World")返回2
    # 但是会将"Hello World"赋值给yield start
    # 因此temp = "Hello World"
    # 那么会打印"Hello World"
    # 并且打印2
    print(ret.send('Hello World'))

send方法和next函数的区别:

  1. send方法可以传递值给yield表达式,而next不可以。
  2. 在第一次执行生成器代码的时候,send函数必须要传一个None进去,而next函数可以不用传。

生成器中的return语句会触发StopIterator异常:

def my_gen(start):
    while start < 3:
        temp = yield start
        # print(temp)
        start += 1
        # 触发StopIterator异常,会导致不能继续遍历
        return "nihao"

ret = my_gen(1)
for x in ret:
    print(x)

生成器的使用案例:

  1. yield实现斐波拉契数列:
    斐波拉契数列的算法:除第一个和第二个数以外,任意一个数都可由前两个数相加得到:1,1,2,3,5,8,13,21,34,55…

用yield做多任务切换:

def neteasy_music(duration):
     c_time = 0
     while c_time < duration:
         print('播放音乐 %d分钟'%c_time)
         yield None
         c_time += 1

def youku_movie(duration):
    c_time = 0
    while c_time < duration:
        print('播放电影 %d分钟' % c_time)
        yield None
        c_time += 1

def main():
    music = neteasy_music(10)
    qq = youku_movie(10)
    music_stop = False
    movie_stop = False
    while True:
        try:
            next(music)
        except StopIteration:
            print('音乐播放完毕')
            music_stop = True

        try:
            next(qq)
        except StopIteration:
            print('电影播放完毕')
            movie_stop = True

        if music_stop and movie_stop:
            break

yield做一个简单的协程:

from urllib.request import urlretrieve

def Consumer(url):
    while True:
        if url:
            filename = url.split('/').pop()
            url = yield urlretrieve(url,filename='images/'+filename)
            print('consumer')

def Producter(consumer):
    # 先执行一下,让后面的send不会报错
    index = 2
    next(consumer)
    while index < 10:
        print('生产者已经获取了第%d个图片链接' % index)
        url = 'http://127.0.0.1:9000/%d.jpg' % index
        consumer.send(url)
        print('producter')
        index += 1

    consumer.close()

 con = Consumer("http://127.0.0.1:9000/1.jpg")
 Producter(con)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值