Python基础 迭代器与生成器
迭代器
可迭代对象(iterable)
但凡是可以返回一个迭代器的对象都可称之为可迭代对象,看个例子
- >>> x = [1, 2, 3]
- >>> y = iter(x)
- >>> z = iter(x)
- >>> next(y)
- 1
- >>> next(y)
- 2
- >>> next(z)
- 1
- >>> type(x)
- <class 'list'>
- >>> type(y)
- <class 'list_iterator'>
这里x是一个可迭代对象,这只是一种通俗的叫法,并不是一种数据类型.
y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。
迭代器(iterator)
那什么是迭代器呢?它是一个带状态的的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__() 和 __ next__() 方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常.
例子
- # 生成无限序列
- >>> from itertools import count
- >>> counter = count(start=13)
- >>> next(counter)
- 13
- >>> next(counter)
- 14
- # 从有限序列中生成无限序列
- >>> from itertools import count
- >>> counter = count(start=13)
- >>> next(counter)
- 13
- >>> next(counter)
- 14
- # 为了更直观地感受迭代器内部的执行过程,我们自定义一个迭代器,以斐波那契数列为例
- class Fib:
- def __init__(self):
- self.prev = 0
- self.curr = 1
-
- def __iter__(self):
- return self
-
- def __next__(self):
- value = self.curr
- self.curr += self.prev
- self.prev = value
- return value
-
- >>> f = Fib()
- >>> list(islice(f, 0, 10))
- [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Fib既是一个可迭代对象(因为它实现了__iter__方法),又是一个迭代器(因为实现了__next__方法)。实例变量prev和curr用户维护迭代器内部的状态。每次调用next()方法的时候做两件事:
1.为下一次调用next()方法修改状态
2 为当前这次调用生成返回结果
生成器
什么是生成器?
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器(实际就是迭代器)
python中的生成器
要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator
- #列表生成式
- lis = [x*x for x in range(10)]
- print(lis)
- #生成器
- generator_ex = (x*x for x in range(10))
- print(generator_ex)
-
- 结果:
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- <generator object <genexpr> at 0x000002A4CBF9EBA0>
那么创建lis和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object
如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:
-
- #生成器
- generator_ex = (x*x for x in range(10))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- print(next(generator_ex))
- 结果:
- 0
- 1
- 4
- 9
- 16
- 25
- 36
- 49
- 64
- 81
- Traceback (most recent call last):
-
- File "列表生成式.py", line 42, in <module>
-
- print(next(generator_ex))
-
- StopIteration
大家可以看到,generator保存的是算法,每次调用next(generaotr_ex)就计算出他的下一个元素的值,直到计算出最后一个元素,没有更多的元素时,抛出StopIteration的错误,而且上面这样不断调用是一个不好的习惯,正确的方法是使用for循环,因为generator也是可迭代对象:
- #生成器
- generator_ex = (x*x for x in range(10))
- for i in generator_ex:
- print(i)
-
- 结果:
- 0
- 1
- 4
- 9
- 16
- 25
- 36
- 49
- 64
- 81
所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
这里说一下generator和函数的执行流程,函数是顺序执行的,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时候从上次的返回yield语句处急需执行,也就是用多少,取多少,不占内存。