通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含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
就可以了:
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 >>>