這篇文章大部分來自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。這個PPT很長並且很是燒腦,建議在閱讀前應瞭解 Python 的生成器與攜程相關知識,推薦《流暢的 Python》。python
使用 yield
來定義一個生成器app
def countdown(n): while n > 0: yield n n -= 1 c = countdown(10) c
<generator object countdown at 0x0000021F5EAB9F10>
生成器可用於迭代函數
for x in countdown(10): print('倒數:', x)
倒數: 10 倒數: 9 倒數: 8 倒數: 7 倒數: 6 倒數: 5 倒數: 4 倒數: 3 倒數: 2 倒數: 1
能夠經過 next()
來一步步地讓生成器 yield
一個值,直到函數迭代器結束並拋出 StopIteration
。若是你對這一頭霧水,建議閱讀《Fluent Python》14.4 章。
這裏 for
其實至關於不斷地調用 next
並處理 StopIteration
命令行
c = countdown(3) # next(c) 3 # next(c) 2 # next(c) 1
你能夠嵌套生成器,這會致使相似於 Unix 命令行管道的效果code
def add_A(seq): for item in seq: yield item + '-A' def add_B(seq): for item in seq: yield item + '-B' def add_C(seq): for item in seq: yield item + '-C' seq = ['apple', 'banana', 'orange'] stacked_generator = add_C(add_B(add_A(seq))) for item in stacked_generator: print(item)
apple-A-B-C banana-A-B-C orange-A-B-C
能夠看到,咱們爲 seq
裏的每項都依次添加了一個 tag。get
yield
的做用是向調用者返回一個值,調用者其實也能夠向生成器傳值。generator
def receiver(): while True: received_item = yield print('收到:', received_item) def caller(): recv = receiver() next(recv) # 使生成器前進到 yield for i in 'abcd': recv.send(i) caller()
收到: a 收到: b 收到: c 收到: d
那 send
函數的返回值是什麼呢?博客
def receiver(): call_times = 0 while True: item = yield call_times print('收到:', item) call_times += 1 def caller(): recv = receiver() next(recv) for i in 'abcd': ret_value = recv.send(i) print('返回值: ', ret_value) caller()
收到: a 返回值: 1 收到: b 返回值: 2 收到: c 返回值: 3 收到: d 返回值: 4
因此 send
能夠向生成器傳值的同時會讓生成器前進到下一個 yield
語句,並將 yield
右側的值做爲返回值。it
yield
用於定義生成器函數yield
存在該函數一定是一個生成器使用 next
使一個生成器前進到下一個 yield
語句處,並將產出值(yielded value)做爲其返回值。使用 gen.__next__()
效果相同。io
注意:這是一個新建立的生成器惟一容許的操做。
def generator(): yield 'a' yield 'b' gen = generator() # next(gen) 'a' # next(gen) 'b'
能夠經過調用生成器的 send
方法來向生成器傳值,這將讓生成器從上次暫停的 yield
前進到下個 yield
,並將產出值做爲 send
的返回值。
def generator(): item = yield 'a' print(item) another_item = yield 'b' gen = generator() print(next(gen)) print(gen.send(1))
a 1 b
經過調用生成器 close
方法能夠生成器在 yield
語句處拋出 GeneratorExit
。這時僅容許 return
,若是沒有捕捉這個錯誤,生成器會靜默關閉,不拋出錯誤。
def generator(): times = 0 while True: yield times times += 1 gen = generator() print(next(gen)) print(next(gen)) gen.close() # 不會拋出錯誤
0 1
調用生成器的 throw
方法能夠在 yield
處拋出某個特定類型的錯誤,若是生成器內部能夠捕捉這個錯誤,那生成器將前進到下個 yield
語句處,並將產出值做爲 throw
的返回值,不然停止生成器。throw
的函數簽名以下:
throw(typ, [,val, [,tb]])
其中 tyb
是某錯誤類型,val
是錯誤信息,tb
爲 traceback。更多信息能夠參考官方的PEP0342
def generator(): try: yield 'apple' except RuntimeError as e: print('捕捉到:', e) yield 'banana' gen = generator() print(next(gen)) print(gen.throw(RuntimeError, '運行錯誤'))
apple 捕捉到: 運行錯誤 banana
若是在生成器函數中加上 return
那在運行到 return
時將會把返回值做爲 StopIteration
的值傳遞出去。這個是 Python3 的特性,Python2 生成器不能返回某個值。
def generator(): yield return 'apple' g = generator() next(g) try: next(g) except StopIteration as e: print(e)
apple
使用 yield from
能夠幫你對一個生成器不斷調用 next
函數,並返回生成器的返回值。言下之意是你能夠在生成器裏調用生成器。
def generator(): yield 'a' yield 'b' return 'c' def func(): result = yield from generator() print(result)
調用 func
結果是返回一個生成器
# func() <generator object func at 0x0000021F5EB0F990> # next(func()) 'a'
另一個例子
def chain(x, y): yield from x yield from y a = [1, 2, 3] b = [4, 5, 6] for i in chain(a, b): print(i, end=' ')
1 2 3 4 5 6
c = [7, 8, 9] for i in chain(a, chain(b, c)): print(i, end=' ')
1 2 3 4 5 6 7 8 9
def generator(): ... yield ... return result
gen = generator() # 使一個生成器前進 next(gen) # 傳遞一個值 gen.send(item) # 停止生成器 gen.close() # 拋出錯誤 gen.throw(exc, val, tb) # 委託 result = yield from gen