基于生成器的協(xié)程
生成器可以作為協(xié)程(coroutine)使用,稱為 "基于生成器的協(xié)程"捂蕴。
協(xié)程和生成器類似譬涡,都是定義體中包含 yield 關(guān)鍵字的函數(shù)。但它們也有本質(zhì)區(qū)別:
- 生成器用于生成 供迭代的數(shù)據(jù)啥辨,next()方法只允許調(diào)用方從生成器中獲取數(shù)據(jù)涡匀;
- 而協(xié)程與迭代無(wú)關(guān),協(xié)程是數(shù)據(jù)的消費(fèi)者溉知,調(diào)用方會(huì)把數(shù)據(jù)推送給協(xié)程陨瘩。
【知識(shí)拓展】:
2001年,Python 2.2 通過(guò)了 PEP 255 -- Simple Generators 级乍,引入了yield 關(guān)鍵字實(shí)現(xiàn)了生成器函數(shù)舌劳,yield 包含 產(chǎn)出 和 讓步
兩個(gè)含義: 生成器中yield x
這行代碼會(huì) 產(chǎn)出 一個(gè)值,提供給 next(...) 的調(diào)用方玫荣; 此外,還會(huì)作出讓步甚淡,暫停執(zhí)行生成器,讓調(diào)用方繼續(xù)工作捅厂,直到需要使用另一個(gè)值時(shí)再調(diào)用 next(...);
2005年贯卦,Python 2.5 通過(guò)了 PEP 342 - "Coroutines via Enhanced Generators"资柔,給生成器增加了.send()、.throw()和.close()方法撵割,第一次實(shí)現(xiàn)了基于生成器的協(xié)程函數(shù)
(generator-based coroutines)
協(xié)程的簡(jiǎn)單使用
- (coroutine function):協(xié)程函數(shù)贿堰;
- (coroutine object):協(xié)程對(duì)象;
生成器的調(diào)用方可以使用.send(...)方法發(fā)送數(shù)據(jù)睁枕,發(fā)送的數(shù)據(jù)會(huì)成為生成器函數(shù)中yield表達(dá)式的值官边。因此,生成器可以作為協(xié)程使用外遇。協(xié)程是指一個(gè)過(guò)程,這個(gè)過(guò)程與調(diào)用方協(xié)作契吉,產(chǎn)出由調(diào)用方提供的值跳仿。
def simple_coroutine():
print("->coroutine started")
x = yield # ①
print("-> coroutine received:", x)
my_coro = simple_coroutine()
next(my_coro) # ②
my_coro.send(42) # ③
OUTPUT:
->coroutine started
Traceback (most recent call last):
-> coroutine received: 42
File "C:/Users/admin/Desktop/untitled1/6-30/coroutine.6-30.py", line 129, in <module>
my_coro.send(42) # ③
StopIteration
【解釋一下】:
① yield在表達(dá)式中使用;如果協(xié)程只需從客戶那里接收數(shù)據(jù)捐晶,那么產(chǎn)出的值是None——這個(gè)值是隱式指定的菲语,因?yàn)閥ield關(guān)鍵字右邊沒(méi)有表達(dá)式。
② 首先要調(diào)用next(...)函數(shù)惑灵,因?yàn)樯善鬟€沒(méi)啟動(dòng)山上,沒(méi)在yield語(yǔ)句處暫停,所以一開(kāi)始無(wú)法發(fā)送數(shù)據(jù)英支。
③ 調(diào)用這個(gè)方法后佩憾,協(xié)程定義體中的yield表達(dá)式會(huì)計(jì)算出42;現(xiàn)在干花,協(xié)程會(huì)恢復(fù)妄帘,一直運(yùn)行到下一個(gè)yield表達(dá)式,或者終止池凄。
協(xié)程與生成器的對(duì)比
- (生成器)generators are data producers:
生成器用于生成供迭代的數(shù)據(jù)抡驼,next()方法只允許調(diào)用方從生成器中獲取數(shù)據(jù);
- (協(xié)程)coroutines are data consumers:
協(xié)程是數(shù)據(jù)的消費(fèi)者肿仑,調(diào)用方會(huì)把數(shù)據(jù)推送給協(xié)程致盟。send()方法允許調(diào)用方和協(xié)程之間雙向交換數(shù)據(jù)。
【注意】: 協(xié)程也可以產(chǎn)出值尤慰,但這與迭代無(wú)關(guān)馏锡;
協(xié)程的狀態(tài)
'GEN_CREATED': 等待開(kāi)始執(zhí)行;
'GEN_RUNNING': 正在被解釋器執(zhí)行割择。只有在多線程應(yīng)用中才能看到這個(gè)狀態(tài)眷篇;
'GEN_SUSPENDED': 在yield表達(dá)式處暫停;
'GEN_CLOSED': 執(zhí)行結(jié)束荔泳;
【注釋】:可以使用 inspect.getgeneratorstate(...) 函數(shù)查看協(xié)程的當(dāng)前狀態(tài)
舉例來(lái)看:
def simple_coroutine():
print("->coroutine started")
x = yield
print(getgeneratorstate(my_coro))
print("-> coroutine received:", x)
my_coro = simple_coroutine()
print(getgeneratorstate(my_coro))
next(my_coro)
print(getgeneratorstate(my_coro))
my_coro.send(27)
print(getgeneratorstate(my_coro))
Output:
GEN_CREATED
Traceback (most recent call last):
->coroutine started
GEN_SUSPENDED
File "C:/Users/admin......(嘻嘻,保密)......py", line 144, in <module>
GEN_RUNNING
my_coro.send(27)
-> coroutine received: 27
StopIteration
【注意點(diǎn)】
因?yàn)閟end方法的參數(shù)會(huì)成為暫停的yield表達(dá)式的值蕉饼,所以虐杯,僅當(dāng)協(xié)程處于暫停狀態(tài)時(shí)才能調(diào)用send方法。
如果給未激活的協(xié)程對(duì)象發(fā)送None以外的值昧港,會(huì)引發(fā)錯(cuò)誤 (避免小錯(cuò)誤哦)擎椰。
激活協(xié)程的方式
- next(...)方法;
- my_coro.send(None)创肥;
小二...給朕上代碼:
def simple_coro2(a):
print('->Started :a = ' ,a)
b = yield a
print('->Started :b = ' ,b)
c = yield a + b
print('->Received: c=',c)
my_coro2 = simple_coro2(14)
next(my_coro2)
rs1 = my_coro2.send(28)
print(rs1)
rs2 = my_coro2.send(99)
print(rs2)
計(jì)算移動(dòng)平均值
def averager():
total = 0.0
count = 0
average = 0.0
while True:
term = yield average
total += term
count += 1
average = total /count
cor_avg = averager()
next(cor_avg)
cor_avg.send(20)
cor_avg.send(5)
cor_avg.send(27)
----
輸出:
20.0
12.5
17.333333333333332
【知識(shí)拓展】:關(guān)于wraps(寫(xiě)的很詳細(xì)):
https://blog.csdn.net/hqzxsc2006/article/details/50337865
Python裝飾器
在實(shí)現(xiàn)的時(shí)候达舒,被裝飾后的函數(shù)其實(shí)已經(jīng)是另外一個(gè)函數(shù)了(函數(shù)名等函數(shù)屬性會(huì)發(fā)生改變),為了不影響叹侄,Python的functools包
中提供了一個(gè)叫wraps的decorator來(lái)消除這樣的副作用巩搏。
寫(xiě)一個(gè)decorator的時(shí)候,最好在實(shí)現(xiàn)之前加上functools的wrap趾代,它能保留原有函數(shù)的名稱和docstring贯底。
#不加wraps
def my_decorator(func):
def wrapper(*args, **kwargs):
"""decorator"""
print("Calling decorated function...")
return func(*args,**kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print('Called example function')
print(example.__name__,example.__doc__)
#輸出:
wrapper decorator
#加wraps
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""decorator"""
print("Calling decorated function...")
return func(*args,**kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print('Called example function')
print(example.__name__,example.__doc__)
Output:
example Docstring
【預(yù)激協(xié)程】
調(diào)用send()方法之前,需要先調(diào)用next(my_coro)方法進(jìn)行激活撒强。有時(shí)候?yàn)榱撕?jiǎn)化協(xié)程的用法禽捆,有時(shí)會(huì)使用一個(gè)預(yù)激裝飾器。
一起來(lái)看一個(gè)例子:
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args,**kwargs):
gen = func(*args,**kwargs)
next(gen)
return gen
return primer
@coroutine
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count
from inspect import getgeneratorstate
coro_avg = averager()
print(getgeneratorstate(coro_avg))
print(coro_avg.send(10))
print(coro_avg.send(30))
Output:
GEN_SUSPENDED
10.0
20.0
【解釋】:如果使用yield from 語(yǔ)法飘哨,會(huì)自動(dòng)預(yù)激胚想。
asyncio.coroutine裝飾器不會(huì)預(yù)激活協(xié)程,因此能兼容yield from 句法芽隆。
終止協(xié)程和異常處理
協(xié)程中未處理的異常會(huì)向上冒泡浊服,傳給next函數(shù)或send方法的調(diào)用方(觸發(fā)協(xié)程的對(duì)象
)。
- 由于第三行發(fā)送的不是數(shù)據(jù)摆马,導(dǎo)致協(xié)程內(nèi)部拋出異常臼闻;
- 由于協(xié)程內(nèi)部沒(méi)有處理異常,協(xié)程會(huì)終止囤采。如果試圖重新激活協(xié)程述呐,會(huì)拋出StopIteration異常;
異常處理的兩種方式
-
generator.throw(exec_type[,exc_value[,traceback]])
會(huì)使生成器在暫停的yield表達(dá)式處拋出指定的異常蕉毯;
如果生成器處理了拋出的異常乓搬,代碼會(huì)向前執(zhí)行到下一個(gè)yield表達(dá)式,而產(chǎn)出的值會(huì)成為調(diào)用generator.throw方法得到的返回值代虾;
如果生成器沒(méi)有處理拋出的異常进肯,異常會(huì)向上冒泡,傳到調(diào)用方的上下文中棉磨;
-
generator.close()
致使生成器在暫停的yield表達(dá)式處拋出GeneratorExit異常(正常終止協(xié)程)江掩。
如果生成器沒(méi)有處理這個(gè)異常,或者拋出了StopIteration異常(通常是指運(yùn)行到結(jié)尾),調(diào)用方不會(huì)報(bào)錯(cuò)环形;
如果收到GeneratorExit異常策泣,生成器一定不能產(chǎn)出值,否則解釋器會(huì)拋出RuntimeError異常抬吟。生成器拋出的其他異常會(huì)向上冒泡萨咕,傳給調(diào)用方。
【注意1】如果收到GeneratorExit異常火本,生成器一定不能產(chǎn)出值危队,否則解釋器會(huì)拋出RuntimeError異常;
生成器拋出的其他異常會(huì)向上冒泡钙畔,傳遞給調(diào)用方茫陆;
【注意2】但是,如果傳入?yún)f(xié)程的異常沒(méi)有處理刃鳄,協(xié)程會(huì)終止盅弛,即狀態(tài)變成 'GEN_CLOSED':
呈上一份代碼:
class DemoException(Exception):
pass
def demo_exc_handing():
print('==>coroutine strated')
while True:
try:
x = yield # 產(chǎn)生異常!
except DemoException: # 特別處理 DemoException 異常
print('==> DemoException handled.Continuing...')
else: # 如果沒(méi)有異常叔锐,那么顯示接收到的值
print('==>coroutine received:{!r}'.format(x))
finally:
print('==>Ending...')
exc_coro = demo_exc_handing()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)
print(inspect.getgeneratorstate(exc_coro))
輸出:
==>Ending...
==>coroutine strated
==>coroutine received:11
==>Ending...
==>coroutine received:22
==>Ending...
GEN_SUSPENDED
【解釋】如果把DemoException異常傳入demo_finally協(xié)程,它會(huì)處理见秽,然后繼續(xù)運(yùn)行
exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)
# 將 DemoException 傳入
exc_coro.throw(DemoException)
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))
仔細(xì)看上述兩者的區(qū)別
【下一步】:如果傳入的異常沒(méi)有處理愉烙,協(xié)程會(huì)立即停止,變成'GEN_CLOSED'狀態(tài)解取。
例如:
exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)
# 將 DemoException 傳入
exc_coro.throw(ZeroDivisionError)
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))
【技能強(qiáng)化】:如果協(xié)程必須做一些清理工作步责,則可以在協(xié)程體中放入try/finally 代碼塊
例如:
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...")
demo_coro = demo_finally();
next(demo_coro)
demo_coro.send(11)
demo_coro.send(ZeroDivisionError)
讓協(xié)程return值
Python 3.3以后,允許在協(xié)程中有return expr表達(dá)式禀苦,如果執(zhí)行到該語(yǔ)句蔓肯,則協(xié)程運(yùn)行結(jié)束。同時(shí)會(huì)拋出StopIteration異常振乏,而返回值就在該異常對(duì)象的value屬性上蔗包。
【重點(diǎn)】如果協(xié)程中有return語(yǔ)句,則返回return后面的表達(dá)式的值慧邮,否則返回None
有些協(xié)程在被激活后调限,每次驅(qū)動(dòng)(drive)協(xié)程時(shí),不會(huì)產(chǎn)出值误澳,而是在最后(協(xié)程正常終止時(shí))返回一個(gè)值(通常是某種累加值)
還是上述講過(guò)的例子耻矮,在這里會(huì)返回平均值:
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(10)
coro_avg.send(30)
coro_avg.send(20)
coro_avg.send(None)
Output:
Traceback (most recent call last):
File "C:/Users/...(保密,嘻嘻)...py", line 25, in <module>
coro_avg.send(None)
StopIteration: Result(count=3, average=20.0)
生成器對(duì)象會(huì)拋出StopIteration異常忆谓。異常對(duì)象的value屬性保存著返回的值.....
【yield from結(jié)構(gòu)】:在內(nèi)部自動(dòng)捕獲StopIteration異常裆装。對(duì)yield from結(jié)構(gòu)來(lái)說(shuō),解釋器不僅會(huì)捕獲StopIteration異常,還會(huì)把value屬性的值變成yield from表達(dá)式的值哨免。
使用協(xié)程的優(yōu)點(diǎn)
- 協(xié)程最大的優(yōu)勢(shì)就是協(xié)程極高的執(zhí)行效率茎活。子程序切換由程序自身控制,因此铁瞒,沒(méi)有線程切換的開(kāi)銷(xiāo)妙色;
- 不需要多線程的鎖機(jī)制,阻塞自動(dòng)切換慧耍;
- 程即是生成器對(duì)象的yield和send方法的配合使用身辨;
【關(guān)于python中的 yield from 句法】:http://www.reibang.com/p/6c6761a407c5