Python中的協程

導語:本文章記錄了本人在學習Python基礎之控制流程篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:python

一、掌握協程的概念與行爲;
二、掌握協程中的預激,終止和異常處理;
三、深刻理解yield from的本質做用。

1、協程介紹

一、協程概述

協程:指的是與調用方協做,產出由調用方提供的值。
語法結構:協程是定義體中包含yield關鍵字的函數,通常使用生成器函數定義。
意義:協程中的yield關鍵字是一種控制流程工具。即無論數據如何流動,協程都會把控制權讓步給中心調度程序,從而激活其餘的協程實現協做式多任務。架構

二、協程的基本行爲

協程包含四種狀態:async

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

可以使用inspect.getgeneratorstate(...)查詢協程所處的狀態。函數

協程中重要的兩個方法:工具

  • .send(datum):調用方把數據提供給協程。
  • next(coroutine):預激協程。

協程返回值:自Python3.3實現PEP 380以來對生成器函數作了兩處改動,一處是生成器能夠返回值。 學習

三、實例1:協程初級使用——計算平均值

下面將利用協程計算用戶傳入若干數值的平均值。ui

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

process=average()
next(process)#預激協程
process.send(5)#輸出5
process.send(10)#輸出7.5
process.send(15) #輸出10.0

小結:協程執行首先須要預激,使之準備好而後讓步控制權。具體地說,協程在yield關鍵字所在的位置暫停執行。在term=yield average這個 賦值語句中,右邊的代碼會在賦值以前執行。 在暫停結束後,從先前阻塞的那行代碼開始,將yield 表達式的值賦給左邊的變量。spa

實例2:令協程返回值設計

from collections import namedtuple

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

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

分析:當協程終止時,能夠在return表達式中返回值。而且return表達式經過把值綁定到StopIteration的value屬性上傳給調用方返回值。事實上這也符合生成器的常規行爲——耗盡時拋出StopIteration異常。code

2、協程的預激、終止與異常處理

一、預激協程

協程在使用前須預激,讓協程向前執行到第一個yield表達式,準備好做爲活躍的協程使用。
預激的本質方法:

  • next(coroutine):常見的標準方法。

同時首次發送coroutine.send(None)也能夠調用next(coroutine),實現相同功能,但缺少可讀性。

基於本質方法,咱們衍生出自定義預激協程的裝飾器的方法,避免忘記預激協程。
coroutine:預激協程的裝飾器

from functools import wraps
def coroutine(func):
    @wraps(func)#把func相關屬性複製過來
    def manage(*args,**kwargs):
        gen=func(*args,**kwargs)#獲取生成器對象
        next(gen)#預激協程
        return gen#返回協程
    return manage

只需將@coroutine語法糖加在生成器函數上,就能夠經過構造生成器對象獲取活躍的協程。
注意:

  • 使用yield from調用協程時會自動預激,所以與@coroutine裝飾器不兼容;
  • Python3.4標準庫中的asyncio.coroutine裝飾器不會預激協程,所以能兼容yield from句法。

二、終止協程

協程中未處理的異常會向上冒泡,傳給next函數或send方法的調用方。
所以,終止協程的本質在於向協程發送其沒法處理的異常。下面介紹三種方法終止協程:

  • 發送哨符值。經常使用None和Ellipsis,甚至StopIteration類也能夠發送。
  • generator.throw(exc_type[,exc_value[,traceback]])
    令生成器在暫停的yield表達式處拋出指定的異常。若生成器處理了此異常,則生成器向前執行到下一個yield表達式,而產出的值會成爲調用generator.throw獲得的返回值。不然,異常會向上冒泡,傳到調用方的上下文中。
  • generator.close()
    令生成器在暫停的yield表達式處拋出GeneratorExit異常。若是生成器不處理此異常,或者跑出來StopIteration,調用方不會報錯。若是收到GeneratorExit異常,生成器必定不能產出值,不然解釋器會拋出RuntimeError異常。

後兩種方法是自Python2.5開始顯式發送異常的兩個方法,建議使用後兩種方法來終止協程。

三、處理異常

在使用協程的過程當中會產生一些須要處理的異常,此時可利用try/except處理。若是無論協程如何結束都要作一些清理工做,請使用try/finally處理。

實例1:使用try/finally在協程終止時執行操做

class DemoException(Exception):
"""爲此次演示定義的異常類型。 """
    def demo_finally():
        print('-> coroutine started')
        try:
            while True:
                try:
                   x = yield
                except DemoException:
                   print('*** DemoException handled. Continuing...')
                else:
                   print('-> coroutine received: {!r}'.format(x))
        finally:
            print('-> coroutine ending')

3、yield from結構

一、yield from結構介紹

做用介紹:
本質做用是打開雙向通道,把最外層的調用方與最內層的子生成器鏈接起來,這樣二者能夠直接發送和產出值,還能夠直接傳入異常。
替代產出值的嵌套for循環。

執行機制:
(1)在生成器gen中使用yield from subgen()時,subgen會得到控制權,把產出的值傳給gen的調用方,即調用方能夠直接控制subgen。與此同時,gen會阻塞,等待subgen終止。
(2)yield from結構會在內部自動捕獲StopIteration異常,還會把對應的value屬性值變成yield from表達式的值。

二、yield from的應用

實例1:對yield from架構雙向通道本質的深刻理解
下面咱們結合實例深刻理解yield from結構。假設咱們須要利用yield from分別計算一個班級男女生身高和體重的平均值,並予以輸出。採用「外部調用方+委派生成器+子生成器」的結構進行設計,結構示意圖以下:
圖片描述
實例代碼:

from collections import namedtuple
Result=namedtuple('Result','average number')

def subaverager():#子生成器經委派生成器處理外部數據,並將值返回給委派生成器。
    total = 0.0
    number = 0
    average = None
    while True:
        term = yield
        if term is None:#外部調用方控制子生成器終止的關鍵語句。
            break
        total += term
        number += 1
        average=total/number
    return Result(average,number)

def averager(results,key):#委託生成器架構雙向通道。
    while True:#避免StopIteration。當獲得子生成器的返回值時,程序會執行到下一個yield。
        results[key]=yield from subaverager()

def main(grouper):
    results={}
    for key,group in grouper.items():
        term = averager(results,key)#構建生成器對象。
        next(term)
        for value in group:
            term.send(value)
        term.send(None)#外部調用方控制子生成器終止的語句。
        print(results)
    result(results)

def result(results):#格式化輸出協程返回的處理數據。
    for key,value in results.items():
        gender,unit=key.split(';')
        print('{} {} averaging {:.2f} {}.'.format(
            value.number,gender,value.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的簡單生成器結束,或者任何的可迭代對象結束。

實例2:替代產出值的嵌套for循環

def gen():
for c in 'AB':
 yield c
 for i in range(1, 3):
 yield i
print(list(gen()))#輸出['A', 'B', 1, 2]

能夠簡化成:

def gen():
yield from 'AB'
yield from range(1, 3)
print(list(gen()))#輸出['A', 'B', 1, 2]

三、PEP380中總結的yield from的六點行爲

  • 子生成器產出的值都直接傳給委派生成器的調用方。
  • 使用send()方法發給委派生成器的值都直接傳給子生成器。若是發送None會調用生成器的__next__()方法。若是發送的不是None,那麼委派生成器恢復運行。任何其餘異常都會向上冒泡,傳給委派生成器。
  • 生成器退出時,生成器中的return表達式會觸發StopIteration(expr)異常拋出。
  • yield from表達式的值是子生成器終止時傳給StopIteration異常的第一個參數。
  • 傳入委派生成器的異常,除了GeneratorExit以外都傳給子生成器的throw()方法。若是調用throw()方法時拋出StopIteration異常,委派生成器恢復運行。StopIteration以外的異常會向上冒泡,傳給委派生成器。
  • 若是把 GeneratorExit 異常傳入委派生成器, 或者在委派生成器上調用 close() 方法, 那麼在子生成器上調用 close() 方法, 若是它有的話。 若是調用 close() 方法致使異常拋出, 那麼異常會向上冒泡, 傳給委派生成器; 不然, 委派生成器拋出GeneratorExit 異常。

4、協程與生成器異同

  • 生成器用於生成供迭代的數據。
  • 協程是數據的消費者,能完成協做式多任務活動。
  • 協程與迭代無關。儘管在協程中會使用 yield 產出值, 但這與迭代無關。
相關文章
相關標籤/搜索