通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含1000万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。
要创建一个generator,有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
1. 简单的列表操作:
1 >>> list1 = [x*x for x in range(10)] 2 >>> list1 3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 4 >>> list2 = [2*x for x in range(12)] 5 >>> list2 6 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22] 7 >>>
2. 生成器操作:
1 >>> list3 = (x*x for x in range(10)) 2 >>> list3 3 <generator object <genexpr> at 0x7f302fdb6cd0> 4 >>> list1 5 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 6 >>> list2 7 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22] 8 >>>
如代码所示,list1和list2是一个列表,而list3是一个generator,在任意时刻我们可以随便打印出list1和list2的列表元素,但是我们怎么才可以打印出list3的元素呢?,在这里generator提供一个打印自身元素的函数,next()。
1 >>> list3 2 <generator object <genexpr> at 0x7f302fdb6cd0> 3 >>> list3.next() 4 0 5 >>> list3.next() 6 1 7 >>> list3.next() 8 4 9 >>> list3.next() 10 9 11 >>> list3.next() 12 16 13 >>> list3.next() 14 25 15 >>> list3.next() 16 36 17 >>> list3.next() 18 49 19 >>> list3.next() 20 64 21 >>> list3.next() 22 81 23 >>> list3.next() 24 Traceback (most recent call last): 25 File "<stdin>", line 1, in <module> 26 StopIteration 27 >>>
generator保存的是算法,每次调用next()
,就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
使用next方法虽然可以计算出生成器的值,但是,实在是太不人性化了,在此,还有没有更好的方法来输出生成器的值呢?方法肯定是有的,那就是采用for迭代输出:
1 >>> list4 = (x*x for x in range(10)) 2 >>> list3 3 <generator object <genexpr> at 0x7f302fdb6cd0> 4 >>> for n in list4: 5 ... print n 6 ... 7 0 8 1 9 4 10 9 11 16 12 25 13 36 14 49 15 64 16 81 17 >>>
所以,我们创建了一个generator后,基本上永远不会调用next()
方法,而是通过for
循环来迭代它。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1 >>> 2 >>> def fibla(max): 3 ... n,a,b = 0,0,1 4 ... while n < max: 5 ... print b 6 ... a,b = b,a+b 7 ... n = n + 1 8 ... 9 >>> fibla(8) 10 1 11 1 12 2 13 3 14 5 15 8 16 13 17 21 18 >>>
观察之后发现,fibla函数其实是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print b
改为yield b
就可以了:
1 >>> def fibla(max): 2 ... n,a,b = 0,0,1 3 ... while n < max: 4 ... print b 5 ... a,b = b,a+b 6 ... n = n + 1 7 ... 8 >>> fibla(8) 9 1 10 1 11 2 12 3 13 5 14 8 15 13 16 21 17 #上下对比 18 >>> def fibla(max): 19 ... n,a,b = 0,0,1 20 ... while n < max: 21 ... yield b 22 ... a,b = b,a+b 23 ... n = n +1 24 ... 25 >>> fibla(8) 26 <generator object fibla at 0x7f302fd5ca00> 27 >>>
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
1 >>> def fibla(max): 2 ... n,a,b = 0,0,1 3 ... while n < max: 4 ... yield b 5 ... a,b = b,a+b 6 ... n = n +1 7 ... 8 >>> fibla(8) 9 <generator object fibla at 0x7f302fd5ca00> 10 >>> for x in fibla(8): 11 ... print x 12 ... 13 1 14 1 15 2 16 3 17 5 18 8 19 13 20 21 21 >>>