導語:本文章記錄了本人在學習Python基礎之控制流程篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。
本文重點:python
一、掌握協程的概念與行爲;
二、掌握協程中的預激,終止和異常處理;
三、深刻理解yield from的本質做用。
協程:指的是與調用方協做,產出由調用方提供的值。
語法結構:協程是定義體中包含yield關鍵字的函數,通常使用生成器函數定義。意義:協程中的yield關鍵字是一種控制流程工具。即無論數據如何流動,協程都會把控制權讓步給中心調度程序,從而激活其餘的協程實現協做式多任務。
架構
協程包含四種狀態:async
可以使用inspect.getgeneratorstate(...)查詢協程所處的狀態。函數
協程中重要的兩個方法:工具
協程返回值:自Python3.3實現PEP 380以來對生成器函數作了兩處改動,一處是生成器能夠返回值。
學習
下面將利用協程計算用戶傳入若干數值的平均值。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
協程在使用前須預激,讓協程向前執行到第一個yield表達式,準備好做爲活躍的協程使用。
預激的本質方法:
同時首次發送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]])
generator.close()
後兩種方法是自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')
做用介紹:本質做用是打開雙向通道,把最外層的調用方與最內層的子生成器鏈接起來,這樣二者能夠直接發送和產出值,還能夠直接傳入異常。
替代產出值的嵌套for循環。
執行機制:(1)在生成器gen中使用yield from subgen()時,subgen會得到控制權,把產出的值傳給gen的調用方,即調用方能夠直接控制subgen。與此同時,gen會阻塞,等待subgen終止。
(2)yield from結構會在內部自動捕獲StopIteration異常,還會把對應的value屬性值變成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]
生成器用於生成供迭代的數據。
協程是數據的消費者,能完成協做式多任務活動。
協程與迭代無關。儘管在協程中會使用 yield 產出值, 但這與迭代無關。