当我在自己的程序中发现用到了模式,我觉得这就表明某个地方出错了。程序的形式应该仅仅反映它所要解决的问题。代码中其他任何外加的形式都是一个信号,(至少对我来说)表明我对问题的抽象还不够深——这通常意味着自己正在手动完成的事情,本应该通过写代码来让宏的扩展自动实现。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 语言内部,迭代器用于支持:
for 循环
构建和扩展集合类型
逐行遍历文本文件
列表推导、字典推导和集合推导
元组拆包
调用函数时,使用 * 拆包实参
本章涵盖以下话题:
语言内部使用 iter(...) 内置函数处理可迭代对象的方式
如何使用 Python 实现经典的迭代器模式
详细说明生成器函数的工作原理
如何使用生成器函数或生成器表达式代替经典的迭代器
如何使用标准库中通用的生成器函数
如何使用 yield from 语句合并生成器
案例分析:在一个数据库转换工具中使用生成器函数处理大型数据集
为什么生成器和协程看似相同,实则差别很大,不能混淆
首先来研究 iter(...) 函数如何把序列变得可以迭代。