有了列表生成式,为什么还要用生成器?
通过列表生成式(list comprehension),我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建生成器:
方法一:
只要把一个列表生成式的[]
改成()
,就创建了一个generator。
>>> L = [x * x for x in range(10)] #列表生成式
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10)) #生成器
>>> g
<generator object <genexpr> at 0x1022ef630>
可以看出,直接打印g并不会返回g内所包含的元素,而是返回一个生成器对象
那么如何打印生成器g内的每一个元素呢?
可以使用next(g)来返回g的下一个元素的值,但是这样很繁琐,而且当没有更多的元素时,抛出StopIteration
的错误。
因为生成器是可迭代对象,更常用的方法是使用for循环:
g = (x*x for x in range(10))
#打印g包含元素
for i in g:
print(g)
方法二:
如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。
#coding=utf-8
#函数功能:输出斐波那契数列的前N个数
#生成器
def fib(max):
n,a,b=0,0,1
while n<max:
yield b
a,b=b,a+b
n+=1
#函数方法
def fib1(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
if __name__=="__main__":
f = fib(10)
print('generator:',f)
#使用for循环打印f内元素
for i in f:
print(i)
#执行结果
"""
generator: <generator object fib at 0x000001602EDFB200>
1
1
2
3
5
8
13
21
34
55
"""
generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
参考:
[2] 《像计算机科学家一样思考Python》第2版 19.3 生成器表达式