一、生成器
通过列表推导,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
二、生成器方法一
第一种方法很简单,只要把一个列表推导的[]
改成()
,就创建了一个generator:
>>> L = [x * x for x in range(5)]
>>> L
[0, 1, 4, 9, 16]
>>> g = (x * x for x in range(5))
>>> g
<generator object <genexpr> at 0x1044299a8>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
我们可以通过next()函数获得generator的下一个返回值,从而获得generator的每一个元素。
>>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
当然,遍历generator正确的方法是使用for循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(5)) >>> for x in g: ... print(x) ... 0 1 4 9 16
我们创建了一个generator后,基本上不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
三、生成器方法二
如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done'
>>> f = fib(6) >>> f <generator object fib at 0x1044299a8>
这里,比较难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
>>> next(f) 1 >>> next(f) 1 >>> next(f) 2 >>> next(f) 3 >>> next(f) 5 >>> next(f) 8 >>> next(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: done
实际上,把函数改成generator后,我们也基本上不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
>>> for n in fib(6): ... print(n) ... 1 1 2 3 5 8
四、总结
generator是非常强大的工具,在Python中,可以简单地把列表推导改成generator,也可以通过函数实现复杂逻辑的generator。
对于列表推导改成的generator来说,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。
对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。
请注意区分普通函数和generator函数,普通函数调用直接返回结果:
>>> r = abs(6) >>> r 6
generator函数的“调用”实际返回一个generator对象:
>>> f = fib(6) >>> f <generator object fib at 0x104429a20>
参考文章(这篇文章比书上讲的更通俗易懂):
https://www.liaoxuefeng.com/wiki/1016959663602400/1017318207388128