能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的。比如说,对字典进行迭代可以得到其所有的键:
>>> some_dict={'a':1,'b':2,'c':3}
>>> for key in some_dict:
... print (key)
...
a
b
c
当你编写for key in some_dict时,Python解释器首先会尝试从some_dict创建一个迭代器:
>>> dict_iterator=iter(some_dict)
>>> dict_iterator
<dict_keyiterator object at 0x7f1674032048>
迭代器是一种特殊对象,它可以在诸如for循环之类的上下文中向Python解释器输送对象。大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象。比如min、max、sum等内置方法以及list、tuple等类型构造器:
>>> list(dict_iterator)
['a', 'b', 'c']
生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的return替换为yield即可:
def squares(n=10):
for i in range(1,n+1):
print("Generating squares from 1 to %d"%(n**2))
yield i**2
调用该生成器时,没有任何代码会被立即执行:
>>> gen=squares()
>>> gen
<generator object squares at 0x7f166f7b6fc0>
直到你从该生成器中请求元素时,它才会开始执行其代码:
>>> for x in gen:
... print(x)
...
Generating squares from 1 to 100
1
Generating squares from 1 to 100
4
Generating squares from 1 to 100
9
Generating squares from 1 to 100
16
Generating squares from 1 to 100
25
Generating squares from 1 to 100
36
Generating squares from 1 to 100
49
Generating squares from 1 to 100
64
Generating squares from 1 to 100
81
Generating squares from 1 to 100
100
假设我们希望找出“将1美元(即100美分)兑换成任意一组硬币”的所有唯一方式。你可能会想出很多种实现方法(包括“已找到的唯一组合”的保存方式)。下面我们编写一个生成器来产生这样的硬币组合(硬币面额用整数表示):
def make_change(amount,coins=[1,5,10,25],hand=None):
hand=[] if hand is None else hand
if amount==0:
yield hand
for coin in coins:
# 确保我们给出的硬币没有超过总额,且组合是唯一的
if coin>amount or (len(hand)>0 and hand[-1]<coin):
continue
for result in make_change(amount-coin,coins=coins,
hand=hand+[coin]):
yield result
这个算法的细节并不重要(你能想出更短点的办法吗?)。然后我们可以编写:
>>> for way in make_change(100,coins=[10,25,50]):
... print (way)
...
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
[25, 25, 10, 10, 10, 10, 10]
[25, 25, 25, 25]
[50, 10, 10, 10, 10, 10]
[50, 25, 25]
[50, 50]
生成器表达式
生成器表达式(generator expression)是构造生成器的最简单方式。生成器也有一个类似于列表、字典、集合推导式的东西,其创建方式为,把列表推导式两端的方括号改成圆括号:
>>> gen=(x**2 for x in range(100))
>>> gen
<generator object <genexpr> at 0x7f166f7b6f10>
它跟下面这个冗长得多的生成器是完全等价的:
def make_gen():
for x in range(100):
yield x**2
gen=make_gen()
生成器表达式可用于任何接受生成器的Python函数:
>>> sum(x**2 for x in range(100))
328350
>>> dict((i,i**2) for i in range(5))
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}