能以一種一致的方式對序列進行迭代(比如列表中的對象或文件中的行)是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}