这章内容开始转向控制流程,探讨可迭代的对象、迭代器和生成器。
迭代器、生成器是近些年程序语言中比较流行的概念。它可以看成一类问题的抽象。C++的STL中,就有迭代器理念。Java中也有迭代器。日益兴旺的JavaScript在ES6版本中,也加入了迭代器、生成器。设计模式里面也包括了迭代器模式。语言通过正交化设计,支持迭代的容器与支持迭代操作的方法,就可以组合在一起使用,而不需要提前设定接口。这种方式,大大方便了编程。迭代,可以看成循环式抽象结构,使用者无须了解底层数据结构,通过定义接口按需一次获取一个数据项。
生成器略有不同。生成器是惰性取值。当然,迭代器可以跟生成器混用,用生成器实现迭代器,生成器也可以看成一种迭代器。它的优点是你不必一次性获取生成要操作的数据。在很多场合,这甚至是很重要的约束。比如你要操作的数据特别大,远远超过设备内存大小,你还能一次性load出来吗?这个时候生成器就有巨大作用了。它可以一次操作一个,反复迭代。而对于调用者来说,它又跟正常的操作逻辑一致 – 可以反复调用,直到最后满足条件。进入内部看,它如同一个可以反复进入的函数,每次进入都延续上次的位置,继续操作。本书作者特别提到:生成器是生成、产生值,如果说“返回”值,会让人难以理解 – 函数返回下次如何进入、延续状态?
在Python中,对象对迭代器对实现是通过__iter__方法进行的。如果没有定义,就会尝试按顺序操作__getitem__方法。两者都失败,会抛出异常。任何Python序列都可以迭代,是因为它们都实现了__getitem__方法。从Python 3.4开始,检查对象x是否可以迭代,最准确的办法是调用:iter(x)函数。
Python的for … in 结构,实际内部是使用了迭代器控制,只是被隐藏了,看不到。构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。迭代器可以迭代,但是可迭代的对象不是迭代器。可迭代的对象一定不能是自身的迭代器,也就是说,可迭代的对象必须实现__iter__方法,但不能实现__next__方法。关于这个主题,可以参考《设计模式》。
本章作者使用了5个版本的Sentence对象案例,来阐述迭代器、生成器的设计和使用。Python使用yield关键字实现了生成器,Python 3.3又提供了新的yield from句法。后面讨论了标准库中itertools库提供的几个有用的操作方法。比如itertools中提供了迭代过程中的逻辑判断方法,可以执行过滤操作。
生成器,还可以用于修改程序运行逻辑、顺序,变成“协程” – Coroutines使用,但是这个时候就跟迭代无关了,纯粹跟yield的运行控制能力有关。
最后面,作者讨论了Python没有把生成器函数与普通函数分离,继续沿用”def”定义带来的弊端。他支持使用”gen”定义生成器函数。