Sentence類第3版:生成器函數
實現相同功能,但卻符合 Python 習慣的方式是,用生成器函數代替
SentenceIterator 類。先看示例 14-5,然后詳細說明生成器函數。
示例 14-5 sentence_gen.py:使用生成器函數實現 Sentence 類
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:def __init__(self, text):self.text = textself.words = RE_WORD.findall(text)def __repr__(self):return 'Sentence(%s)' % reprlib.repr(self.text)def __iter__(self):for word in self.words: ?yield word ?return ?
# 完成! ?
? 迭代 self.words。
? 產出當前的 word。
? 這個 return 語句不是必要的;這個函數可以直接“落空”,自動返
回。不管有沒有 return 語句,生成器函數都不會拋出 StopIteration
異常,而是在生成完全部值之后會直接退出。
? 不用再單獨定義一個迭代器類!
我們又使用一種不同的方式實現了 Sentence 類,而且也能通過示例
14-2 中的測試。
在示例 14-4 定義的 Sentence 類中,__iter__
方法調用
SentenceIterator 類的構造方法創建一個迭代器并將其返回。而在示
例 14-5 中,迭代器其實是生成器對象,每次調用 __iter__
方法都會
自動創建,因為這里的 __iter__
方法是生成器函數。
下面全面說明生成器函數。
生成器函數的工作原理
只要 Python 函數的定義體中有 yield 關鍵字,該函數就是生成器函
數。調用生成器函數時,會返回一個生成器對象。也就是說,生成器函
數是生成器工廠。
普通的函數與生成器函數在句法上唯一的區別是,在后者的
定義體中有 yield 關鍵字。有些人認為定義生成器函數應該使用
一個新的關鍵字,例如 gen,而不該使用 def,但是 Guido 不同
意。他的理由參見“PEP 255—Simple
Generators”(https://www.python.org/dev/peps/pep-0255/)。
下面以一個特別簡單的函數說明生成器的行為
>>> def gen_123(): # ?
... yield 1 # ?
... yield 2
... yield 3
...
>>> gen_123 # doctest: +ELLIPSIS
<function gen_123 at 0x...> # ?
>>> gen_123() # doctest: +ELLIPSIS
<generator object gen_123 at 0x...> # ?
>>> for i in gen_123(): # ?
... print(i)
123 >>> g
=
gen_123() #
?
>>> next(g) # ?
1 >>> next(g)
2 >>> next(g)
3 >>> next(g) #
?
Traceback (most recent call last):
...
StopIteration
? 只要 Python 函數中包含關鍵字 yield,該函數就是生成器函數。
? 生成器函數的定義體中通常都有循環,不過這不是必要條件;這里
我重復使用 3 次 yield。
? 仔細看,gen_123 是函數對象。
? 但是調用時,gen_123() 返回一個生成器對象。
? 生成器是迭代器,會生成傳給 yield 關鍵字的表達式的值。
? 為了仔細檢查,我們把生成器對象賦值給 g。
? 因為 g 是迭代器,所以調用 next(g) 會獲取 yield 生成的下一個元
素。
? 生成器函數的定義體執行完畢后,生成器對象會拋出
StopIteration 異常。
生成器函數會創建一個生成器對象,包裝生成器函數的定義體。把生成
器傳給 next(…) 函數時,生成器函數會向前,執行函數定義體中的
下一個 yield 語句,返回產出的值,并在函數定義體的當前位置暫
停。最終,函數的定義體返回時,外層的生成器對象會拋出
StopIteration 異常——這一點與迭代器協議一致。
我覺得,使用準確的詞語描述從生成器中獲取結果的過程,
有助于理解生成器。注意,我說的是產出或生成值。如果說生成
器“返回”值,就會讓人難以理解。函數返回值;調用生成器函數返
回生成器;生成器產出或生成值。生成器不會以常規的方式“返
回”值:生成器函數定義體中的 return 語句會觸發生成器對象拋
出 StopIteration 異常。
示例 14-6 使用 for 循環更清楚地說明了生成器函數定義體的執行過
程。
示例 14-6 運行時打印消息的生成器函數
>>> def gen_AB(): # ?
... print('start')
... yield 'A' # ?
... print('continue')
... yield 'B' # ?
... print('end.') # ?
...
>>> for c in gen_AB(): # ?
... print('-->', c) # ?
...
start ?
--> A ?
continue ?
--> B ?
end. ?
>>> ?
? 定義生成器函數的方式與普通的函數無異,只不過要使用 yield 關鍵字。
? 在 for 循環中第一次隱式調用 next() 函數時(序號?),會打印
‘start’,然后停在第一個 yield 語句,生成值 ‘A’。
? 在 for 循環中第二次隱式調用 next() 函數時,會打印
‘continue’,然后停在第二個 yield 語句,生成值 ‘B’。
? 第三次調用 next() 函數時,會打印 ‘end.’,然后到達函數定義體
的末尾,導致生成器對象拋出 StopIteration 異常。
? 迭代時,for 機制的作用與 g = iter(gen_AB()) 一樣,用于獲取
生成器對象,然后每次迭代時調用 next(g)。
? 循環塊打印 --> 和 next(g) 返回的值。但是,生成器函數中的
print 函數輸出結果之后才會看到這個輸出。
? ‘start’ 是生成器函數定義體中 print(‘start’) 輸出的結果。
? 生成器函數定義體中的 yield ‘A’ 語句會生成值 A,提供給 for 循
環使用,而 A 會賦值給變量 c,最終輸出 --> A。
? 第二次調用 next(g),繼續迭代,生成器函數定義體中的代碼由
yield ‘A’ 前進到 yield ‘B’。文本 continue 是由生成器函數定義
體中的第二個 print 函數輸出的。
? yield ‘B’ 語句生成值 B,提供給 for 循環使用,而 B 會賦值給變
量 c,所以循環打印出 --> B。
? 第三次調用 next(it),繼續迭代,前進到生成器函數的末尾。文本
end. 是由生成器函數定義體中的第三個 print 函數輸出的。到達生成
器函數定義體的末尾時,生成器對象拋出 StopIteration 異常。for
機制會捕獲異常,因此循環終止時沒有報錯。
? 現在,希望你已經知道示例 14-5 中 Sentence.__iter__
方法的作
用了:__iter__
方法是生成器函數,調用時會構建一個實現了迭代器
接口的生成器對象,因此不用再定義 SentenceIterator 類了。這一版 Sentence 類比前一版簡短多了,但是還不夠懶惰。如今,人們
認為惰性是好的特質,至少在編程語言和 API 中是如此。惰性實現是指
盡可能延后生成值。這樣做能節省內存,而且或許還可以避免做無用的
處理。