生成器(generator)也是一種迭代器,在每次迭代時返回一個值,直到拋出 StopIteration
異常。它有兩種構(gòu)造方式:
和列表推導(dǎo)式的定義類似,生成器表達式使用 ()
而不是 []
,比如:
numbers = (x for x in range(5)) # 注意是(),而不是[]
for num in numbers:
print num
含有 yield
關(guān)鍵字的函數(shù),調(diào)用該函數(shù)時會返回一個生成器。
本文主要講生成器函數(shù)。
先來看一個簡單的例子:
>>> def generator_function():
... print 'hello 1'
... yield 1
... print 'hello 2'
... yield 2
... print 'hello 3'
>>>
>>> g = generator_function() # 函數(shù)沒有立即執(zhí)行,而是返回了一個生成器,當(dāng)然也是一個迭代器
>>> g.next() # 當(dāng)使用 next() 方法,或使用 next(g) 的時候開始執(zhí)行,遇到 yield 暫停
hello 1
1
>>> g.next() # 從原來暫停的地方繼續(xù)執(zhí)行
hello 2
2
>>> g.next() # 從原來暫停的地方繼續(xù)執(zhí)行,沒有 yield,拋出異常
hello 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
可以看到,上面的函數(shù)沒有使用 return
語句返回值,而是使用了 yield
『生出』一個值。一個帶有 yield
的函數(shù)就是一個生成器函數(shù),當(dāng)我們使用 yield
時,它幫我們自動創(chuàng)建了 __iter__()
和 next()
方法,而且在沒有數(shù)據(jù)時,也會拋出 StopIteration
異常,也就是我們不費吹灰之力就獲得了一個迭代器,非常簡潔和高效。
帶有 yield
的函數(shù)執(zhí)行過程比較特別:
next()
(在 for 循環(huán)中會自動調(diào)用 next()
) 作用于返回的生成器對象時,函數(shù)開始執(zhí)行,在遇到 yield
的時候會『暫?!?,并返回當(dāng)前的迭代值;next()
的時候,函數(shù)會從原來『暫?!坏牡胤嚼^續(xù)執(zhí)行,直到遇到 yield
語句,如果沒有 yield
語句,則拋出異常;整個過程看起來就是不斷地 執(zhí)行->中斷->執(zhí)行->中斷
的過程。一開始,調(diào)用生成器函數(shù)的時候,函數(shù)不會立即執(zhí)行,而是返回一個生成器對象;然后,當(dāng)我們使用 next()
作用于它的時候,它開始執(zhí)行,遇到 yield
語句的時候,執(zhí)行被中斷,并返回當(dāng)前的迭代值,要注意的是,此刻會記住中斷的位置和所有的變量值,也就是執(zhí)行時的上下文環(huán)境被保留起來;當(dāng)再次使用 next()
的時候,從原來中斷的地方繼續(xù)執(zhí)行,直至遇到 yield
,如果沒有 yield
,則拋出異常。
簡而言之,就是 next
使函數(shù)執(zhí)行,yield
使函數(shù)暫停。
看一個 Fibonacci 數(shù)列的例子,如果使用自定義迭代器的方法,是這樣的:
>>> class Fib(object):
... def __init__(self):
... self.a, self.b = 0, 1
... def __iter__(self):
... return self
... def next(self):
... self.a, self.b = self.b, self.a + self.b
... return self.a
...
>>> f = Fib()
>>> for item in f:
... if item > 10:
... break
... print item
...
1
1
2
3
5
8
而使用生成器的方法,是這樣的:
>>> def fib():
... a, b = 0, 1
... while True:
... a, b = b, a + b
... yield a
...
>>> f = fib()
>>> for item in f:
... if item > 10:
... break
... print item
...
1
1
2
3
5
8
可以看到,使用生成器的方法非常簡潔,不用自定義 __iter__()
和 next()
方法。
另外,在處理大文件的時候,我們可能無法一次性將其載入內(nèi)存,這時可以通過構(gòu)造固定長度的緩沖區(qū),來不斷讀取文件內(nèi)容。有了 yield
,我們就不用自己實現(xiàn)讀文件的迭代器了,比如下面的實現(xiàn):
def read_in_chunks(file_object, chunk_size=1024):
"""Lazy function (generator) to read a file piece by piece.
Default chunk size: 1k."""
while True:
data = file_object.read(chunk_size)
if not data:
break
yield data
f = open('really_big_file.dat')
for piece in read_in_chunks(f):
process_data(piece)
我們除了能對生成器進行迭代使它返回值外,還能:
send()
方法給它發(fā)送消息;throw()
方法給它發(fā)送異常;close()
方法關(guān)閉生成器;看一個簡單的例子:
>>> def generator_function():
... value1 = yield 0
... print 'value1 is ', value1
... value2 = yield 1
... print 'value2 is ', value2
... value3 = yield 2
... print 'value3 is ', value3
...
>>> g = generator_function()
>>> g.next() # 調(diào)用 next() 方法開始執(zhí)行,返回 0
0
>>> g.send(2)
value1 is 2
1
>>> g.send(3)
value2 is 3
2
>>> g.send(4)
value3 is 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
在上面的代碼中,我們先調(diào)用 next()
方法,使函數(shù)開始執(zhí)行,代碼執(zhí)行到 yield 0
的時候暫停,返回了 0;接著,我們執(zhí)行了 send()
方法,它會恢復(fù)生成器的運行,并將發(fā)送的值賦給上次中斷時 yield 表達式的執(zhí)行結(jié)果,也就是 value1,這時控制臺打印出 value1 的值,并繼續(xù)執(zhí)行,直到遇到 yield 后暫停,此時返回 1;類似地,再次執(zhí)行 send()
方法,將值賦給 value2。
簡單地說,send()
方法就是 next()
的功能,加上傳值給 yield
。
除了可以給生成器傳值,我們還可以給它傳異常,比如:
>>> def generator_function():
... try:
... yield 'Normal'
... except ValueError:
... yield 'Error'
... finally:
... print 'Finally'
...
>>> g = generator_function()
>>> g.next()
'Normal'
>>> g.throw(ValueError)
'Error'
>>> g.next()
Finally
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
可以看到,throw()
方法向生成器函數(shù)傳遞了 ValueError
異常,此時代碼進入 except ValueError 語句,遇到 yield 'Error',暫停并返回 Error 字符串。
簡單的說,throw()
就是 next()
的功能,加上傳異常給 yield
。
我們可以使用 close()
方法來關(guān)閉一個生成器。生成器被關(guān)閉后,再次調(diào)用 next() 方法,不管能否遇到 yield 關(guān)鍵字,都會拋出 StopIteration 異常,比如:
>>> def generator_function():
... yield 1
... yield 2
... yield 3
...
>>> g = generator_function()
>>>
>>> g.next()
1
>>> g.close() # 關(guān)閉生成器
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
執(zhí)行->中斷->執(zhí)行->中斷
的過程。
next()
作用于它的時候,它開始執(zhí)行,遇到 yield
語句的時候,執(zhí)行被中斷,并返回當(dāng)前的迭代值,要注意的是,此刻會記住中斷的位置和所有的數(shù)據(jù),也就是執(zhí)行時的上下文環(huán)境被保留起來;next()
的時候,從原來中斷的地方繼續(xù)執(zhí)行,直至遇到 yield
,如果沒有 yield
,則拋出異常。