第 14 章 可迭代的对象、迭代器和生成器

当我在自己的程序中发现用到了模式,我觉得这就表明某个地方出错了。程序的形式应该仅仅反映它所要解决的问题。代码中其他任何外加的形式都是一个信号,(至少对我来说)表明我对问题的抽象还不够深——这通常意味着自己正在手动完成的事情,本应该通过写代码来让宏的扩展自动实现。1

——Paul Graham2
Lisp 黑客和风险投资人

1摘自一篇博客文章,“Revenge of the Nerds”(“书呆子的复仇”,http://www.paulgraham.com/icad.html)。

2Paul Graham 的文集《黑客与画家:来自计算机时代的高见》已由人民邮电出版社出版,书号:978-7-115-32656-0。——编者注

迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。本章说明 Python 语言是如何内置迭代器模式的,这样就避免了自己手动去实现。

与 Lisp(Paul Graham 最喜欢的语言)不同,Python 没有宏,因此为了抽象出迭代器模式,需要改动语言本身。为此,Python 2.2(2001 年)加入了 yield 关键字。3 这个关键字用于构建生成器(generator),其作用与迭代器一样。

3Python 2.2 的用户可以使用 from __future__ import generators 指令获取 yield 关键字;在 Python 2.3 中,yield 关键字默认可用。

 所有生成器都是迭代器,因为生成器完全实现了迭代器接口。不过,根据《设计模式:可复用面向对象软件的基础》一书的定义,迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数列中的数有无穷个,在一个集合里放不下。不过要知道,在 Python 社区中,大多数时候都把迭代器生成器视作同一概念。

在 Python 3 中,生成器有广泛的用途。现在,即使是内置的 range() 函数也返回一个类似生成器的对象,而以前则返回完整的列表。如果一定要让 range() 函数返回列表,那么必须明确指明(例如,list(range(100)))。

在 Python 中,所有集合都可以迭代。在 Python 语言内部,迭代器用于支持:

本章涵盖以下话题:

首先来研究 iter(...) 函数如何把序列变得可以迭代。