Python 學習筆記 關於協程

協程

定義:協程是指一個過程,這個過程與調用方協做,產出由調用方提供的值。(協程中一定含有一條yield語句)python

協程與生成器相似,都是定義體內包含yield關鍵字的函數。不過,在協程中,yield一般出如今表達式的右邊(例如,data = yield),能夠產出值,也能夠不產出。函數

  • 生成器不能夠返回值,若是生成器中給return語句提供值,會拋出SyntaxError異常;
  • python新引入yield from 語句,能夠把複雜的生成器重構成小型的嵌套生成器,省去了大量樣板代碼。

三個方法:

  • . send() 方法,可讓調用方給協程發送數據,發送的數據會成爲協程函數中 yield 表達式的值。
  • .throw() 方法,可讓調用方拋出異常
  • .close() 方法,可讓調用方終止協程

四個狀態:

  • 'GEN_CREATED' 等待開始執行
  • 'GEN_RUNNING' 解釋器正在執行
  • 'GEN_SUSPENDED' 在yield表達式處暫停
  • 'GEN_CLOASED' 執行結束

協程只能處於這四個狀態中的一個,當前狀態能夠由 inspect.getgeneratorstate(...)函數獲取debug

由於send() 方法的參數會成爲暫停的yield表達式的值,因此,僅當協程處於暫停狀態時才能調用send()方法code

協程須要被預激,預激是經過next()函數進行orm

給協程添加預激裝飾器 functools.wraps(),能夠省去協程的預激過程。協程

yield from

在生成器gen中使用yield from subgen()時,subgen()會獲得當前的控制權,把產出的值傳給gen的調用方,即調用方能夠直接跳過gen控制subgen。當subgen獲得控制權時,gen會阻塞,同時等待subgen終止。get

一個小例子:generator

def chain(*iters):
    for iter in iters:
        yield from iter

lst_1 = 'abc'
lst_2 = '987'
print(list(chain(lst_1, lst_2)))

運行結果:it

['a', 'b', 'c', '9', '8', '7']

這個例子還能夠改寫爲:io

def chain():
    yield from 'abc'
    yield from '987'

輸出結果是同樣的。

yield from 的主要功能是打開雙向通道,把最外層的調用方與最內層的子生成器鏈接起來,這樣,兩者能夠直接發送和產生值,甚至能夠直接傳入異常。

一個複雜的例子,計算中學生的平均身高和體重:

from collections import namedtuple

Result = namedtuple('Result', 'count average')


# 子生成器
def averager():  # <1>
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # <2>
        if term is None:  # <3>
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)  # <4>


# 委派生成器
def grouper(results, key):  # <5>
    while True:  # <6>
        results[key] = yield from averager()  # <7>


# 客戶端代碼,即調用端
def main(data):  # <8>
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # <9>
        next(group)  # <10>
        for value in values:
            group.send(value)  # <11>
        group.send(None)  # important! <12>

    # print(results)  # uncomment to debug
    report(results)


# 輸出報告
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
              result.count, group, result.average, unit))


data = {
    'girls;kg':
        [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m':
        [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg':
        [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m':
        [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}


if __name__ == '__main__':
    main(data)

運行結果:

9 boys  averaging 40.42kg
 9 boys  averaging 1.39m
10 girls averaging 42.04kg
10 girls averaging 1.43m
  • 委派生成器grouper()只是起到一個傳輸數據的做用,沒有進行任何的數據處理。

生成器中都有一個無限循環 while True: 這個無限循環代表,只要調用方不斷把值發送給這個協程,它就會一直接收值,而後生成結果。該循環結束條件:

  • 調用方在協程上顯式調用 .close() 方法,
  • 或者沒有對協程的引用,而被垃圾回收程序回收時,這個協程纔會終止。

終止協程的方法

generator.close()

該方法導致生成器在暫停的 yield 表達式處拋出 GeneratorExit 異常。

  • 若是生成器處理了這個異常,生成器必定不能產生值,不然解釋器會拋出RuntimeError異常。
  • 若是生成器沒有處理這個異常,或者拋出StopIteration異常,即生成器已經運行到最後,調用方也不會報錯。
相關文章
相關標籤/搜索