流暢的python讀書筆記-第十六章-攜(協)程

協程

協程能夠身處四個狀態中的一個。併發

當前狀態能夠使用inspect.getgeneratorstate(...) 函數肯定,該函數會返回下述字符串中的一個。app

'GEN_CREATED'
  等待開始執行。函數

'GEN_RUNNING'
  解釋器正在執行。spa

'GEN_SUSPENDED'
  在 yield 表達式處暫停。線程

'GEN_CLOSED'
  執行結束。調試

def simple_coro2(a):
    print('-> Started: a =', a)


    b = yield a  #等着賦值b 把a甩出去
    print('-> Received: b =', b)
    c = yield a + b
    print('-> Received: c =', c)


my_coro_2 = simple_coro2(14)

from inspect import getgeneratorstate

print(getgeneratorstate(my_coro_2))

print(next(my_coro_2))
getgeneratorstate(my_coro_2)


print(my_coro_2.send(28))

# 沒有yield 出來 因此沒有返回值
print(my_coro_2.send(99))

getgeneratorstate(my_coro_2)

getgeneratorstate 函數指明,處於 GEN_SUSPENDED 狀態(即協程在 yield 表達式處暫停)。code

❺ 把數字 99 發給暫停的協程;計算 yield 表達式,獲得 99,而後把那個數綁定給 c。
打印 -> Received: c = 99 消息,而後協程終止,致使生成器對象拋出
StopIteration 異常。orm

clipboard.png

另外一個案例

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count


coro_avg = averager()

print(next(coro_avg))

print(coro_avg.send(10))

print(coro_avg.send(15))

print(coro_avg.send(20))

調用 next 函數,預激協程。
➊ 這個無限循環代表,只要調用方不斷把值發給這個協程,它就會一直接收值,而後生
成結果。僅當調用方在協程上調用 .close() 方法,或者沒有對協程的引用而被垃圾回收
程序回收時,這個協程纔會終止。
➋ 這裏的 yield 表達式用於暫停執行協程,把結果發給調用方;還用於接收調用方後面
發給協程的值,恢復無限循環。協程

終止協程和異常處理

發送某個哨符值,讓協程退出。
內置的 None 和Ellipsis 等常量常常用做哨符值。對象

Ellipsis 的優勢是,數據流中不太常有這個值。

throw

generator.throw(exc_type[, exc_value[, traceback]])

導致生成器在暫停的 yield 表達式處拋出指定的異常。

若是生成器處理了拋出的異常,代碼會向前執行到下一個 yield 表達式,而產出的值會成爲調用 generator.throw方法獲得的返回值。

generator.close()

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

若是生成器沒有處理這個異常,或者拋出了 StopIteration 異常(一般是指運行到結尾),調用方不會報錯。

若是收到 GeneratorExit 異常,生成器必定不能產出值,不然解釋器會拋出
RuntimeError 異常。

兩種中止方式

exc_coro.throw(ZeroDivisionError)
exc_coro.close()

讓協程返回值

from collections import namedtuple

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


def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)


coro_avg = averager()
next(coro_avg)

coro_avg.send(30)
coro_avg.send(6.5)

try:
    coro_avg.send(None)
except StopIteration as exc:
    result = exc.value
    print(result)

捕獲 StopIteration 異常,獲取 averager 返回的值

yield from 結構會在內部自動捕獲 StopIteration 異常。
這種處理方式與 for 循環處理 StopIteration 異常的方式同樣:循環機制使用用戶易於理解的方式處理異常。
對 yield from 結構來講,解釋器不只會捕獲 StopIteration 異常,還會把value 屬性的值變成 yield from 表達式的值。

使用yield from

yield from 結構惟一的做用是替代產出值的嵌套 for 循環, 這句話不對

yield from 的主要功能是打開雙向通道,把最外層的調用方與最內層的子生成器鏈接起來,

這樣兩者能夠直接發送和產出值,還能夠直接傳入異常,

而不用在位於中間的協程中添加大量處理異常的樣板代碼。

有了這個結構,協程能夠經過之前不可能的方式委託職責。

案例

委派生成器
  包含 yield from <iterable> 表達式的生成器函數。

子生成器
  從 yield from 表達式中 <iterable> 部分獲取的生成器。這就是 PEP 380 的標題
(「Syntax for Delegating to a Subgenerator」)中所說的「子生成器」(subgenerator)。

調用方
  PEP 380 使用「調用方」這個術語指代調用委派生成器的客戶端代碼。在不一樣的語境
中,我會使用「客戶端」代替「調用方」,以此與委派生成器(也是調用方,由於它調用了子
生成器)區分開。

clipboard.png

不使用yield from

from collections import namedtuple

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


# 子生成器
def averager():  # ➊
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # ➋
        if term is None:  # ➌
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)  # ➍



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],
}




def main(data):
    result = {}

    for key, values in data.items():
        coro_avg = averager()
        next(coro_avg)
        for value in values:
            coro_avg.send(value)

        try:
            coro_avg.send(None)
        except StopIteration as exc:
            result[key] = exc.value

    print(result)



if __name__ == '__main__':
    main(data)
  1. 這裏的try: catch stop異常要一直存在

用yiled from 及委派生成器做用

from collections import namedtuple

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


# 子生成器
def averager():  # ➊
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # ➋
        if term is None:  # ➌
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)  # ➍



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],
}


def grouper(results):
    while True:
        res_obj = yield from averager()
        results.append(res_obj)




def main(data):
    results = []
    for key, values in data.items():

        coro_avg = grouper(results)
        next(coro_avg)

        for value in values:
            coro_avg.send(value)

        # 這個None是中止返回 哨兵
        coro_avg.send(None)


    print(results)

    # report(result)


# 輸出報告
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))


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

官方的案例

from collections import namedtuple

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


# 子生成器
def averager():  # ➊
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # ➋
        if term is None:  # ➌
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)  # ➍


# 委派生成器
def grouper(results, key):  # ➎
    while True:  # ➏
        results[key] = yield from averager()  # ➐




# 客戶端代碼,即調用方
def main(data):  # ➑
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # ➒
        next(group)  # ➓
        for value in values:
            group.send(value)  # ⓫
        group.send(None)  # 重要! #⓬

    print(results)  # 若是要調試,去掉註釋

    # 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)

❸ 相當重要的終止條件。若是不這麼作,使用 yield from 調用這個協程的生成器會永
遠阻塞。
❹ 返回的 Result 會成爲 grouper 函數中 yield from 表達式的值

❻ 這個循環每次迭代時會新建一個 averager 實例;每一個實例都是做爲協程使用的生成
器對象。
❼ grouper 發送的每一個值都會經由 yield from 處理,經過管道傳給 averager 實
例。grouper 會在 yield from 表達式處暫停,等待 averager 實例處理客戶端發來的
值。averager 實例運行完畢後,返回的值綁定到 results[key] 上。while 循環會不斷
建立 averager 實例,處理更多的值。

yield from 結構會在內部自動捕獲 StopIteration 異常。這種處理方
式與 for 循環處理 StopIteration 異常的方式同樣:循環機制使用用戶易於理解的方式
處理異常。對 yield from 結構來講,解釋器不只會捕獲 StopIteration 異常,還會把
value 屬性的值變成 yield from 表達式的值。

總結

這裏先不總結 帶我把協程 程序加進去

如何使用協程在單個線程中管理併發活動。

相關文章
相關標籤/搜索