python的生成器

本文深入探讨Python生成器的两大优势:节省内存和简化代码。通过对比列表与生成器实现斐波那契数列,展示生成器如何在处理大规模数据时有效减少内存使用。并解析生成器的工作原理,包括yield关键字的作用及生成器的两种构建方式。

生成器算是python中比较实用的一个工具,但由于其他主流编程语言中没有“生成器”这个概念,所以生成器经常容易被忽视。

那么使用生成器有什么好处呢?我认为好处有两点:其一是减少内存的使用,其二是简化代码,同时可以增强代码的可读性

举个栗子,比如要生成一个斐波那契数列,如果不用生成器,而是用列表实现的话,代码大致如下:

def fib(n):
    prev, cur = 0, 1
    res = []
    while n > 0:
        n -= 1
        res.append(cur)
        prev, cur = cur, prev + cur
    return res

print(fib(10))
#Out:[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

如上述代码所示,如果使用列表这个数据结构,那么需要在内存中保存10个int类型的数据,你可能说这也就40个字节,不打紧;但是如果这个型参n的传入值为1000000000(十亿),那就需要占用大概4GB的内存,电脑是不太容易吃得消的。

因此,对于数据量特别庞大的数据来说,用列表显然非常耗费内存。这里生成器就有用武之地了,我不需要一次性得到这么多的数据,只是在用到的时候拿出来就好了。

如果改造成利用生成器实现斐波那契数列,代码可以改写为:

def fib(n):
    prev, cur = 0, 1
    while n > 0:
        n -= 1
        yield cur
        prev, cur = cur, prev + cur
   
g = fib(10)
print([i for i in g)])
#Out:[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

上面的代码是利用生成器函数得到一个生成器(有两种构建生成器的方法,下面会有介绍)。从上面的代码可以看出,生成器函数中有一个关键字yield,这是表明这个函数是生成器函数的标志,yield可以返回一个值(与return类似);除此以外,每当执行到yield,相当于对这个函数状态进行挂起,即暂时不再执行下去(并不是完全结束),需要有一个事件来唤醒这个函数。

而唤醒这个函数的事件就是迭代器协议中的next(),我们知道,生成器是满足迭代器协议的,即可以调用内置的next()函数来获取下一个元素的值,在python中,对于一个迭代器做for循环,相当于不断调用其next()函数,直到迭代器的下一项为空,此时会抛出一个StopIteration的异常来终止迭代。

因此,for循环每执行一次,相当于对满足迭代器的对象g做一次next(),此时会唤醒经过yield后挂起的函数,使得生成器函数继续向下执行。这样就看出了生成器可以大大节省内存空间,因为生成器是按照需要产生数据的一个对象,每一次调用next()只返回当前需要用到的数值即可,而不用保存整个数列的信息;同时上面生成器的代码显然要更加简洁。

下面一段代码可能对理解next()与挂起有着更好的帮助:

def array(n):
    for i in range(10):
        print("before return", i)
        yield i
        print("after return", i)

g = array(10)
print(g)	#g一开始表示生成器对象的地址
#Out:<generator object array at 0x115a4e1b0>

print(next(g))
"""
before return 0
0
"""

print(next(g))
"""
after return 0
before return 1
1
"""

print(next(g))
"""
after return 1
before return 2
2
"""

通过上面的代码可以看出,第一次调用next()函数时,yield一行将0返回以后,暂时中止了向下执行,也就是说yield使生成器函数变为挂起状态。当第二次执行next()函数,就唤醒了生成器函数,从上一次中止的地方继续向下执行。

创建生成器的两种方式

第一种是利用生成器函数(关键字yield),上面已经介绍过,这里不赘述啦。

第二种是生成器表达式,例如求一个2的n次方的数列,代码可以写为:

g1 = (pow(2, x) for x in range(10))
print([i for i in g1])
#Out:[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

和产生列表的方式比较类似,只不过将方括号改为圆括号即可。

注意事项

python中的生成器只能遍历一遍,看下面一段代码:

g1 = (pow(2, x) for x in range(10))
print([i for i in g1])
#Out:[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
print([i for i in g1])
#Out:[]

可以发现,第二次遍历生成器g1的时候,就已经失去了效果。因为在第一次遍历后,已经指向了结束的位置,所以第二次遍历将不会再有结果。

总结

python生成器的好处大致是节省内存,简化代码;

生成器的构造有两种方法:生成器函数(含yield关键字)和生成器表达式(圆括号);

生成器满足迭代器协议,即可以通过next()函数访问下一个值。如果使用yield关键字,在返回一个值的同时挂起该生成器函数,直到下一次next()来唤醒该函数,之后在刚才中断的地方继续向下执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值