生成器算是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()来唤醒该函数,之后在刚才中断的地方继续向下执行。
本文深入探讨Python生成器的两大优势:节省内存和简化代码。通过对比列表与生成器实现斐波那契数列,展示生成器如何在处理大规模数据时有效减少内存使用。并解析生成器的工作原理,包括yield关键字的作用及生成器的两种构建方式。

被折叠的 条评论
为什么被折叠?



