Python中的迭代器和生成器
本博客为Python核心技术实战的学习笔记,如需获取全文,请点击链接。
迭代器
所有的容器都是可迭代(iterable)的。迭代和枚举不同,进行迭代时,并不知道被迭代对象有的容量是多少,只是每次进行迭代时,被迭代对象都返回一个元素或者迭代完成的标志(StopIteration)。
对于可迭代的对象,可以使用iter()
函数得到一个迭代器,再调用next()
函数便可以实现对可迭代对象的遍历。我们常使用的for
循环便是将这一过程隐式化。
1 | items = [1, 2 ,3] |
生成器
什么是生成器?
在C++等语言中都有迭代器的概念,而对于生成器来说,很多语言中则没有对应的实现。
生成器是懒人版本的迭代器。
在迭代器中,如果我们想要对其元素进行枚举,就需要提前生成这些元素。如下述程序:
1 | import os |
1 | def test_iterator(): |
在上述程序中可以发现,迭代器提前生成了所有的元素,这会消耗大量的内存。而生成器则不会提前生成所有元素。生成器只有在调用next()
函数时才会生成下一个变量,同时生成器在初始化的时候不需要运行一次生成操作,耗时更短。
在Python中,我们可以使用小括号来初始化一个生成器:
1 | (i for i in range(100000000)) |
自定义生成器
首先给出如下代码:
1 | def generator(k): |
在上述代码中,generator()
函数即创建了一个生成器。在这个函数中有一个关键字yeild
,可以理解为,当程序运行到这一行时会暂停,跳出程序。跳出的目的地是next()
函数,yield
后跟的i ** k
即为next()
函数的返回值。
当每次调用next()
函数时,程序将会从上一次暂停的地方继续运行,并且,其中的i
变量并未被清除。
与迭代器的另一个不同之处是,迭代器是一个有限的集合,而生成器可以成为一个无限集。只要调用next()
函数,生成器便会执行一次操作。
下面解决一个常见的问题,给定一个列表,求一个特定的数字在这个列表中的位置。
常规做法是暴力遍历,如下所示:
1 |
|
使用生成器的做法如下:
1 |
|
上述代码中使用list()
将生成器对象转换为了列表。
另一个问题,给定两个序列,判定第一个是不是第二个的子序列。子序列指的是:一个列表的元素在第二个列表中按照顺序出现,但是不必要挨在一起。
最简单的做法是贪心算法,维持两个指针指向两个列表的最开始,对第二个列表进行遍历,当与第一个列表的指针指向的元素匹配时,第一个列表的指针后移一位,然后第二个列表的指针继续从当前位置向后遍历,当第一个列表的指针遍历完所有的元素时,返回True
,否则返回False
。
1 | def is_subsequence(a, b): |
如果使用迭代器和生成器,则写法如下:
1 | def is_subsequence(a, b): |
将上述代码展开:
1 |
|
其中的(i in b)
语句,相当于下述代码:
1 | while True: |
首先使用iter(b)
将b
转换为迭代器,这样在next()
函数运行时,就可以保存当前运行到的b
的值,下一次运行到val = next(b)
时会接着上一次的指针继续往下。
1 |
|
上述代码中,b
是一个生成器,第一次调用2 in b
生成到2的时候就停止,接着调用4 in b
时,生成到4时停止,此时生成器已经消耗完毕,再次调用3 in b
便会返回False
。
- 容器是可迭代对象,可迭代对象调用
iter()
函数可以得到迭代器。对迭代器调用next()
函数可以得到下一个元素。 - 生成器是一种特殊的迭代器,使用生成器可以降低内存占用、优化程序结构、提高程序速度。
- 对于有限的生成器来说,当遍历完成后,再次调用
next()
函数会触发raise StopIteration
,必须使用iter()
对生成器进行复位。
迭代器和生成器都是调用一次
next()
返回一次值,下次调用会接着上一次的状态继续执行。in
和for
都可以隐式调用next()
函数。