鍍金池/ 教程/ Python/ 生成器
基礎(chǔ)
itertools
HTTP 服務(wù)
hashlib
閉包
文件和目錄
單元測試
使用 @property
標(biāo)準(zhǔn)模塊
陌生的 metaclass
Base64
進程、線程和協(xié)程
讀寫二進制文件
匿名函數(shù)
輸入和輸出
Click
元組
字符編碼
partial 函數(shù)
參考資料
collections
協(xié)程
類和實例
Python 之旅
定制類和魔法方法
常用數(shù)據(jù)類型
繼承和多態(tài)
ThreadLocal
HTTP 協(xié)議簡介
Requests 庫的使用
讀寫文本文件
列表
os 模塊
迭代器 (Iterator)
正則表達式
集合
上下文管理器
異常處理
你不知道的 super
定義函數(shù)
datetime
資源推薦
字典
slots 魔法
hmac
第三方模塊
進程
類方法和靜態(tài)方法
函數(shù)參數(shù)
高階函數(shù)
函數(shù)
re 模塊
高級特性
線程
argparse
生成器
結(jié)束語
字符串
map/reduce/filter
函數(shù)式編程
Celery
裝飾器

生成器

生成器(generator)也是一種迭代器,在每次迭代時返回一個值,直到拋出 StopIteration 異常。它有兩種構(gòu)造方式:

  • 生成器表達式

和列表推導(dǎo)式的定義類似,生成器表達式使用 () 而不是 [],比如:

numbers = (x for x in range(5))   # 注意是(),而不是[]
for num in numbers:
    print num
  • 生成器函數(shù)

含有 yield 關(guān)鍵字的函數(shù),調(diào)用該函數(shù)時會返回一個生成器。

本文主要講生成器函數(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í)行過程比較特別:

  • 調(diào)用該函數(shù)的時候不會立即執(zhí)行代碼,而是返回了一個生成器對象;
  • 當(dāng)使用 next() (在 for 循環(huán)中會自動調(diào)用 next()) 作用于返回的生成器對象時,函數(shù)開始執(zhí)行,在遇到 yield 的時候會『暫?!?,并返回當(dāng)前的迭代值;
  • 當(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)閉生成器;

send() 方法

看一個簡單的例子:

>>> 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。

throw() 方法

除了可以給生成器傳值,我們還可以給它傳異常,比如:

>>> 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() 方法

我們可以使用 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

小結(jié)

  • yield 把函數(shù)變成了一個生成器。
  • 生成器函數(shù)的執(zhí)行過程看起來就是不斷地 執(zhí)行->中斷->執(zhí)行->中斷 的過程。
    • 一開始,調(diào)用生成器函數(shù)的時候,函數(shù)不會立即執(zhí)行,而是返回一個生成器對象;
    • 然后,當(dāng)我們使用 next() 作用于它的時候,它開始執(zhí)行,遇到 yield 語句的時候,執(zhí)行被中斷,并返回當(dāng)前的迭代值,要注意的是,此刻會記住中斷的位置和所有的數(shù)據(jù),也就是執(zhí)行時的上下文環(huán)境被保留起來;
    • 當(dāng)再次使用 next() 的時候,從原來中斷的地方繼續(xù)執(zhí)行,直至遇到 yield,如果沒有 yield,則拋出異常。

參考資料

上一篇:標(biāo)準(zhǔn)模塊下一篇:argparse