generator自PEP 255引入以来,一直是Python非常重要的组成部分。
generator允许我们定义一个函数,使其成为一个迭代器(iterator)。这是一种更快,更简洁的创建迭代器的方法。
那么迭代器又是什么呢?
迭代器是可以在其上进行循环的对象。也就是说,我们可以通过for循环对其进行遍历。与此同时,与可迭代(iterable)对象(string, list, tuple等)不同,迭代器可以通过next()函数获取下一个值。
迭代器由实现了迭代器协议(iterator protocol)的类定义。该协议要求在类中实现两个方法:__iter__和__next__。
既然我们已经有了可迭代对象,为什么还需要创建迭代器呢?原因在于,迭代器可以节省内存空间。
迭代器并不是在初始化的时候就将所有元素的值都计算出来,而是只有在数据被需要时才进行计算。这就是所谓的惰性计算(lazy evaluation)。
惰性计算之所以重要,是因为当我们在操作一个规模较大的数据集合的时候,它允许我们可以立刻对数据进行读取,而不必等到所有数据都计算完成。
比如我们想计算小于某个值的所有质数。
首先,我们定义一个函数来判断一个数是否是质数:
然后我们定义一个迭代器类,并且实现__iter__和__next__方法:
如果下一个质数大于等于max,迭代器将抛出一个StopIteration异常,从而结束迭代。
通过迭代器,我们不需要创建一个list把所有质数保存在内存里。而是仅仅在每次请求下一个质数的时候才生成这个值。
在循环中,每轮迭代Primes对象都会调用__next__方法去产生下一个质数。
迭代器只能被遍历一次。如果你试图再次对其进行遍历,那么将无法得到任何结果。
现在我们知道迭代器是什么了,接下来让我们进入generator的话题。
generator
generator引入了yield指令。yield和return比较类似,它们都会从函数返回一个值。
不同之处在于,yield会保存函数当前的执行状态。当函数再次被调用时,执行将从上次返回之处开始,并保持有变量的值都与yield之前相同。
让我们来把Primes改造成一个generator:
是不是看起来好多了?
更进一步的,利用PEP 289引入的generator表达式(generator expressions),我们可以做得更好。
下列代码可以达到与generator函数相同的效果:
看,这就是Python generator之美!
总结
- generator允许我们优雅地创建迭代器。
- generator采用惰性计算,只有在需要时才计算下一个值,非常适合处理大数据集合。
- 迭代器和generator只能被遍历一次。
- generator函数可以产生迭代器。
- generator表达式在特定场景下可以替代generator函数并且实现更为简单。
欢迎大家关注我的头条号并就这一话题进行讨论。