凭脑海中的印象回忆这三个概念:
可迭代对象
凡是实现了可迭代协议 __iter__
的对象都是可迭代对象(通常是序列型的对象),实现了__iter__
协议的方法都可以通过for ... in ...
语句进行遍历。
但是是否一定要实现
__iter__
才能用迭代呢?
并不是,因为python太智能了。。。如果对一个类实现了一个__getitem__
方法,python会在for遍历的时候查找有没有__iter__
,没有的话会尝试通过__getitem__
不断传入0开始的整数来支持for遍历。一个对象如果看起来稍微像个序列,python会尽量尝试帮它实现序列的功能。
可迭代对象在for遍历时隐式调用了特殊方法__iter__
,拿到该方法的返回值——一个迭代器,接着不断next()
拿到序列中的下一个值,直到迭代器抛出了最后一个元素后,再next()
将抛出StopIteration
异常,for语句捕获这个异常,判定遍历结束。
所以可迭代对象的实现背后是有迭代器支持的,只不过我们看不到。
迭代器
迭代器是一个惰性序列,迭代器最初并不知道它包含的元素的值,也不关心元素的数目,每调用next()时才会即时计算下一个可用的元素并返回,这使得迭代器是一种更节约空间的高效序列,同时也意味着可以实现包含无限元素的序列。可以使用list()方法强制计算出迭代器的所有值。如果迭代完所有元素,这个迭代器就废了,要想重头开始只能构建新的迭代器。
实现了迭代器协议:__iter__
和__next__
的对象都是迭代器。
说明所有的迭代器必然也是一个可迭代对象。它的__iter__
方法返回它本身即可,它的__next__
会在next()这个迭代器的时候被隐式调用,返回迭代器中的下一个值。
怎么获得一个迭代器:
最原始的方法,手写一个自定义类,实现迭代器协议即可。
另一种方法是通过iter()
函数传入一个可迭代对象,返回一个迭代器。
但是这两种方法其实都不常用。。。更符合python习惯的是使用生成器。
生成器
生成器实际上也是一种迭代器。它让我们不用很蠢地去手写一个迭代器类,而是通过方便的生成器函数和生成器表达式获得一个迭代器。生成器表达式适合用于逻辑简单的生成器,使用生成器函数可以构造复杂的生成器。
生成器函数是怎么不断“生成”值的?
只要包含了yield的函数就是生成器函数,生成器函数的定义体内一般会有循环,不断yield抛出多个值(当然也可以只有一个yield)。用于“生成”的生成器函数一般只在
yield
右侧有值:yield x
。而没有必要写成y = yield x
用y保存yield的返回值,一般这种写法更偏向于协程功能。
只是调用生成器函数并不会执行函数体中的代码,然后返回了一个生成器 gen(注意:返回的不是生成器函数的return value),调用方接下来可以通过next(gen)
或gen.send(x)
来驱动这个gen,不过现在send的作用还没有体现出来,send方法更大的作用是实现协程功能时让协程与调用方“互动”。
当调用方第一次调用next(gen)时,生成器函数中的代码会从头开始执行,执行完第一个yield语句的右侧,把yield expression
语句expression
的值抛出next()的返回值,然后保持挂起。
直到调用方调用下一个next(),生成器函数中的代码会从上次挂起的地方继续执行到下一个yield的右侧,抛出一个值接着再次挂起。
最后一个yield语句抛出值后,再调用next()会跑完生成器函数中的所有代码——将抛出StopIteration
异常。
生成器函数的return value保存在StopIteration
异常对象中,可以try…expect捕获异常拿到return value。
以前看过一道面试题问迭代器和生成器的区别,这题真的不明白想问什么,生成器明明就是一种迭代器。大部分时间这两个概念可以说是通用的,这类问题简直沙雕,不知所云。
硬要说的话:
1.生成器实现逻辑更清晰,简单,通过生成器函数或生成器表达式,python自动替你实现迭代器协议的两个特殊方法,比如用生成器替换__iter__
所需要的迭代器是一个方便的选择。
2.利用生成器函数,生成器可以加入和“产出”没有关系的额外功能,比如用于实现协程功能。
3.迭代器偏向于为集合服务,从序列中抛出元素;而生成器用于“凭空”生成元素。
标准库中一些好用的生成器:
itertools包中的
1.cycle(iterable)
——循环产出。
2.filter(predicate, iterable)
——过滤
3.chain(iter1,iter2,...)
——首尾相连
内置的
1.enumrate(iterablre, start)
——包装成tuple——(index, item)不断产出。
2.map
函数——映射
3.zip
函数——拉链(两个链条不一样长时还有zip_longest可以选择)