玩转python(4)生成器

生成器是python中一个很有趣的概念,不只是有趣,而且很实用。
我们经常需要生成数组,一般来说,数组占据内存的大小和数组的长度有直接关系。数组中的元素越多,长度越长,占据的空间也越大。下面的代码中,我们初始化3个列表,并分别存放10,100,1000个元素:

import sysl1 = list() l2 = list() l3 = list()for i in range(10): l1.append(i)for i in range(100): l2.append(i)for i in range(1000): l3.append(i)print('size of l1:'+str(sys.getsizeof(l1))) print('size of l2:'+str(sys.getsizeof(l2))) print('size of l3:'+str(sys.getsizeof(l3)))

输出如下结果:
size of l1:192 size of l2:912 size of l3:9024

可以预见,当数组内的元素变得更多时,数组占用的空间会继续增大。如果开发者在程序运行之前不能确定数组长度的范围,那么这种动态数组会为程序的运行带来极高的风险。类似的还有读取文件,如果文件很大,直接读入内存可能会直接导致程序的崩溃。对于这个问题,聪明的程序员们自然不会坐以待毙,他们提出了这样的解决方案:如果列表元素可以按照某种算法推算出来,那就可以在循环的过程中不断推算出后续的元素,避免创建完整的数组,从而节省空间。于是生成器的概念就产生了。接下来举一个很有代表性的例子:计算斐波那契数列。网上关于迭代器的资料几乎都提到了这个例子。
def fib(max): n, a, b = 0, 0, 1 while n < max: print b a, b = b, a + b n = n + 1fib(6)

【玩转python(4)生成器】得到的结果为:
1 1 2 3 5 8

fib函数定义了斐波拉契数列的推算规则,每一次循环都可以打印对应的值。不过用print()并不适合函数的复用,而return又会直接导致函数返回。于是python提供了yield关键字,它的功能和return类似,也是向调用者“返回”值,一个函数一旦带上yield关键字,它就不再是一个普通函数,而是一个生成器(具体原理以后介绍)。大致的流程是,生成器在调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。切记不要从函数的角度去看待生成器,这样会很难理解,实在难以接受的话可以把函数当作一个“线程”,遇到yield则“中断”,调用next()继续执行,直至结束为止。接下来,我们写一个生成器,看看它到底有什么好处:
import sysdef generator(): i = 0 while i < 10000: yield i i += 1g = generator print('size of generator:'+str(sys.getsizeof(g)))

输出结果为:
size of generator:136

这个生成器可以生成0-9999共10000个数字,占用存储空间比上面的数组l1还要小。其实,第一个例子中的range()函数也是生成器:
import sysprint('size of range(10):'+str(sys.getsizeof(range(10)))) print('size of range(100):'+str(sys.getsizeof(range(100)))) print('size of range(1000):'+str(sys.getsizeof(range(1000))))

结果显示range(10),range(100),range(1000)占用的空间是相同的。
size of range(10):48 size of range(100):48 size of range(1000):48

所以,如果一个可迭代对象能够以生成器的方式实现,那就尽量用生成器实现。下一篇博文我会具体说说生成器的原理。

    推荐阅读