生成器進化到協程 Part 1
來自專欄筆記
前言
這篇文章大部分來自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。這個PPT很長而且非常燒腦,建議在閱讀前應了解 Python 的生成器與攜程相關知識,推薦《流暢的 Python》。你也可以點擊
生成器進化到協程 Part 1 | PythonCaff - 高品質的 Python 開發者社區
來閱讀這篇文章原文。
- David Beazley 的博客
- PPT 下載鏈接
生成器(generator)
使用 yield
來定義一個生成器
def countdown(n): while n > 0: yield n n -= 1c = 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 命令行管道的效果
def add_A(seq): for item in seq: yield item + -Adef add_B(seq): for item in seq: yield item + -Bdef add_C(seq): for item in seq: yield item + -Cseq = [apple, banana, orange]stacked_generator = add_C(add_B(add_A(seq)))for item in stacked_generator: print(item)apple-A-B-Cbanana-A-B-Corange-A-B-C
可以看到,我們為 seq
里的每項都依次添加了一個 tag。
yield 可以接受傳值
yield
的作用是向調用者返回一個值,調用者其實也可以向生成器傳值。
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 += 1def 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` 右側的值作為返回值。## 生成器 101- `yield` 用於定義生成器函數- 只要 `yield` 存在該函數必定是一個生成器- 調用該函數返回一個生成器### 讓一個生成器前進使用 `next` 使一個生成器前進到下一個 `yield` 語句處,並將產出值(yielded value)作為其返回值。使用 `gen.__next__()`效果相同。**注意**:這是一個新創建的生成器唯一允許的操作。```pythondef generator(): yield a yield bgen = generator()# next(gen)a# next(gen)b
給生成器傳值
可以通過調用生成器的 send
方法來向生成器傳值,這將讓生成器從上次暫停的 yield
前進到下個 yield
,並將產出值作為 send
的返回值。
def generator(): item = yield a print(item) another_item = yield bgen = generator()print(next(gen))print(gen.send(1))a1b
關閉一個生成器
通過調用生成器 close
方法可以生成器在 yield
語句處拋出 GeneratorExit
。這時僅允許 return
,如果沒有捕捉這個錯誤,生成器會靜默關閉,不拋出錯誤。
def generator(): times = 0 while True: yield times times += 1gen = generator()print(next(gen))print(next(gen))gen.close() # 不會拋出錯誤01 ```### 拋出錯誤調用生成器的 `throw` 方法可以在 `yield` 處拋出某個特定類型的錯誤,如果生成器內部可以捕捉這個錯誤,那生成器將前進到下個 `yield` 語句處,並將產出值作為 `throw` 的返回值,否則中止生成器。`throw` 的函數簽名如下:```pythonthrow(typ, [,val, [,tb]])
其中 tyb
是某錯誤類型,val
是錯誤信息,tb
為 traceback。更多信息可以參考官方的PEP0342
def generator(): try: yield apple except RuntimeError as e: print(捕捉到:, e) yield bananagen = generator()print(next(gen))print(gen.throw(RuntimeError, 運行錯誤))apple捕捉到: 運行錯誤banana
生成器返回值
如果在生成器函數中加上 return
那在運行到 return
時將會把返回值作為 StopIteration
的值傳遞出去。這個是 Python3 的特性,Python2 生成器不能返回某個值。
def generator(): yield return appleg = generator()next(g)try: next(g)except StopIteration as e: print(e)apple
生成器委託
使用 yield from
可以幫你對一個生成器不斷調用 next
函數,並返回生成器的返回值。言下之意是你可以在生成器里調用生成器。
def generator(): yield a yield b return cdef 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 ya = [1, 2, 3]b = [4, 5, 6]for i in chain(a, b): print(i, end= )1 2 3 4 5 6c = [7, 8, 9]for i in chain(a, chain(b, c)): print(i, end= )1 2 3 4 5 6 7 8 9
Part 1總結
生成器定義
def generator(): ... yield ... return result
生成器操作
gen = generator()# 使一個生成器前進next(gen)# 傳遞一個值gen.send(item)# 中止生成器gen.close()# 拋出錯誤gen.throw(exc, val, tb)# 委託result = yield from gen
推薦閱讀: