生成器進化到協程 Part 1

生成器進化到協程 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

推薦閱讀:

常規操作之非同步
【js-非同步編程】 孤獨的人,不要寫非同步
Node.js 非同步異聞錄

TAG:Python | 非同步 | 協程 |