[轉載] Python中協程的詳細用法和例子
從句法上看,協程與生成器類似,都是定義體中包含 yield 關鍵字的函式。可是,在協程中, yield 通常出現在表示式的右邊(例如, datum = yield),可以產出值,也可以不產出 —— 如果 yield 關鍵字後面沒有表示式,那麼生成器產出 None。
協程可能會從呼叫方接收資料,不過呼叫方把資料提供給協程使用的是 .send(datum) 方法,而不是next(…) 函式。
= =yield 關鍵字甚至還可以不接收或傳出資料。不管資料如何流動, yield 都是一種流程控制工具,使用它可以實現協作式多工:協程可以把控制器讓步給中心排程程式,從而啟用其他的協程= =。
協程的生成器的基本行為
這裡有一個最簡單的協程程式碼:
def simple_coroutine():
print('-> start')
x = yield
print('-> recived', x)
sc = simple_coroutine()
next(sc)
sc.send('zhexiao')
解釋:
協程使用生成器函式定義:定義體中有 yield 關鍵字。yield 在表示式中使用;如果協程只需從客戶那裡接收資料,那麼產出的值是 None —— 這個值是隱式指定的,因為 yield 關鍵字右邊沒有表示式。首先要呼叫 next(…) 函式,因為生成器還沒啟動,沒在 yield 語句處暫停,所以一開始無法傳送資料。呼叫send方法,把值傳給 yield 的變數,然後協程恢復,繼續執行下面的程式碼,直到執行到下一個 yield 表示式,或者終止。
注意:send方法只有當協程處於 GEN_SUSPENDED 狀態下時才會運作,所以我們使用 next() 方法啟用協程到 yield 表示式處停止,或者我們也可以使用 sc.send(None),效果與 next(sc) 一樣。
協程的四個狀態:
協程可以身處四個狀態中的一個。當前狀態可以使用inspect.getgeneratorstate(…) 函式確定,該函式會返回下述字串中的一個:
GEN_CREATED:等待開始執行GEN_RUNNING:直譯器正在執行GEN_SUSPENED:在yield表示式處暫停GEN_CLOSED:執行結束
最先呼叫 next(sc) 函式這一步通常稱為“預激”(prime)協程(即,讓協程向前執行到第一個 yield 表示式,準備好作為活躍的協程使用)。
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:857662006
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
import inspect
def simple_coroutine(a):
print('-> start')
b = yield a
print('-> recived', a, b)
c = yield a + b
print('-> recived', a, b, c)
# run
sc = simple_coroutine(5)
next(sc)
sc.send(6) # 5, 6
sc.send(7) # 5, 6, 7
示例:使用協程計算移動平均值
def averager():
total = 0.0
count = 0
avg = None
while True:
num = yield avg
total += num
count += 1
avg = total/count
# run
ag = averager()
# 預激協程
print(next(ag)) # None
print(ag.send(10)) # 10
print(ag.send(20)) # 15
解釋:
呼叫 next(ag) 函式後,協程會向前執行到 yield 表示式,產出 average 變數的初始值——None。此時,協程在 yield 表示式處暫停。使用 send() 啟用協程,把傳送的值賦給 num,並計算出 avg 的值。使用 print 列印出 yield 返回的資料。
終止協程和異常處理
協程中未處理的異常會向上冒泡,傳給 next 函式或 send 方法的呼叫方(即觸發協程的物件)。
終止協程的一種方式:傳送某個哨符值,讓協程退出。內建的 None 和Ellipsis 等常量經常用作哨符值。
顯式地把異常發給協程
從 Python 2.5 開始,客戶程式碼可以在生成器物件上呼叫兩個方法,顯式地把異常發給協程。
generator.throw(exc_type[, exc_value[, traceback]])
致使生成器在暫停的 yield 表示式處丟擲指定的異常。如果生成器處理了丟擲的異常,程式碼會向前執行到下一個 yield 表示式,而產出的值會成為呼叫 generator.throw方法得到的返回值。如果生成器沒有處理丟擲的異常,異常會向上冒泡,傳到呼叫方的上下文中。
generator.close()
致使生成器在暫停的 yield 表示式處丟擲 GeneratorExit 異常。如果生成器沒有處理這個異常,或者丟擲了 StopIteration 異常(通常是指執行到結尾),呼叫方不會報錯。如果收到 GeneratorExit 異常,生成器一定不能產出值,否則直譯器會丟擲RuntimeError 異常。生成器丟擲的其他異常會向上冒泡,傳給呼叫方。
異常處理示例:
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:857662006
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
class DemoException(Exception):
"""
custom exception
"""
def handle_exception():
print('-> start')
while True:
try:
x = yield
except DemoException:
print('-> run demo exception')
else:
print('-> recived x:', x)
raise RuntimeError('this line should never run')
he = handle_exception()
next(he)
he.send(10) # recived x: 10
he.send(20) # recived x: 20
he.throw(DemoException) # run demo exception
he.send(40) # recived x: 40
he.close()
如果傳入無法處理的異常,則協程會終止:
he.throw(Exception) # run demo exception
yield from獲取協程的返回值
為了得到返回值,協程必須正常終止;然後生成器物件會丟擲StopIteration 異常,異常物件的 value 屬性儲存著返回的值。
yield from 結構會在內部自動捕獲 StopIteration 異常。對 yield from 結構來說,直譯器不僅會捕獲 StopIteration 異常,還會把value 屬性的值變成 yield from 表示式的值。
yield from基本用法
在生成器 gen 中使用 yield from subgen() 時, subgen 會獲得控制權,把產出的值傳給 gen 的呼叫方,即呼叫方可以直接控制 subgen。與此同時, gen 會阻塞,等待 subgen 終止。
下面2個函式的作用一樣,只是使用了 yield from 的更加簡潔:
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:857662006
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
def gen():
for c in 'AB':
yield c
print(list(gen()))
def gen_new():
yield from 'AB'
print(list(gen_new()))
yield from x 表示式對 x 物件所做的第一件事是,呼叫 iter(x),從中獲取迭代器,因此, x 可以是任何可迭代的物件,這只是 yield from 最基礎的用法==。
yield from高階用法
yield from 的主要功能是開啟雙向通道,把最外層的呼叫方與最內層的子生成器連線起來,這樣二者可以直接傳送和產出值,還可以直接傳入異常,而不用在位於中間的協程中新增大量處理異常的樣板程式碼。
yield from 專門的術語
委派生成器:包含 yield from 表示式的生成器函式。子生成器:從 yield from 中 部分獲取的生成器。
圖示: 解釋:
委派生成器在 yield from 表示式處暫停時,呼叫方可以直接把資料發給子生成器。子生成器再把產出的值發給呼叫方。子生成器返回之後,直譯器會丟擲 StopIteration 異常,並把返回值附加到異常物件上,此時委派生成器會恢復。
高階示例
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:857662006
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
from collections import namedtuple
ResClass = namedtuple('Res', '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 ResClass(count, average)
# 委派生成器
def grouper(storages, key):
while True:
# 獲取averager()返回的值
storages[key] = yield from averager()
# 客戶端程式碼
def client():
process_data = {
'boys_2': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
'boys_1': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46]
}
storages = {}
for k, v in process_data.items():
# 獲得協程
coroutine = grouper(storages, k)
# 預激協程
next(coroutine)
# 傳送資料到協程
for dt in v:
coroutine.send(dt)
# 終止協程
coroutine.send(None)
print(storages)
# run
client()
解釋:
外層 for 迴圈每次迭代會新建一個 grouper 例項,賦值給 coroutine 變數; grouper 是委派生成器。呼叫 next(coroutine),預激委派生成器 grouper,此時進入 while True 迴圈,呼叫子生成器 averager 後,在 yield from 表示式處暫停。內層 for 迴圈呼叫 coroutine.send(value),直接把值傳給子生成器 averager。同時,當前的 grouper 例項(coroutine)在 yield from 表示式處暫停。內層迴圈結束後, grouper 例項依舊在 yield from 表示式處暫停,因此, grouper函式定義體中為 results[key] 賦值的語句還沒有執行。coroutine.send(None) 終止 averager 子生成器,子生成器丟擲 StopIteration 異常並將返回的資料包含在異常物件的value中,yield from 可以直接抓取 StopItration 異常並將異常物件的 value 賦值給 results[key]
yield from的意義
子生成器產出的值都直接傳給委派生成器的呼叫方(即客戶端程式碼)。使用 send() 方法發給委派生成器的值都直接傳給子生成器。如果傳送的值是None,那麼會呼叫子生成器的 next() 方法。如果傳送的值不是 None,那麼會呼叫子生成器的 send() 方法。如果呼叫的方法丟擲 StopIteration 異常,那麼委派生成器恢復執行。任何其他異常都會向上冒泡,傳給委派生成器。生成器退出時,生成器(或子生成器)中的 return expr 表示式會觸發 StopIteration(expr) 異常丟擲。yield from 表示式的值是子生成器終止時傳給 StopIteration 異常的第一個引數。傳入委派生成器的異常,除了 GeneratorExit 之外都傳給子生成器的 throw() 方法。如果呼叫 throw() 方法時丟擲 StopIteration 異常,委派生成器恢復執行。 StopIteration 之外的異常會向上冒泡,傳給委派生成器。如果把 GeneratorExit 異常傳入委派生成器,或者在委派生成器上呼叫 close() 方法,那麼在子生成器上呼叫 close() 方法,如果它有的話。如果呼叫close()方法導致異常丟擲,那麼異常會向上冒泡,傳給委派生成器;否則,委派生成器丟擲GeneratorExit 異常。
使用案例
協程能自然地表述很多演算法,例如模擬、遊戲、非同步 I/O,以及其他事件驅動型程式設計形式或協作式多工。協程是 asyncio 包的基礎構建。通過模擬系統能說明如何使用協程代替執行緒實現併發的活動。
在模擬領域,程式這個術語指代模型中某個實體的活動,與作業系統中的程式無關。模擬系統中的一個程式可以使用作業系統中的一個程式實現,但是通常會使用一個執行緒或一個協程實現。
計程車示例
import collections
# time 欄位是事件發生時的模擬時間,
# proc 欄位是計程車程式例項的編號,
# action 欄位是描述活動的字串。
Event = collections.namedtuple('Event', 'time proc action')
def taxi_process(proc_num, trips_num, start_time=0):
"""
每次改變狀態時建立事件,把控制權讓給模擬器
:param proc_num:
:param trips_num:
:param start_time:
:return:
"""
time = yield Event(start_time, proc_num, 'leave garage')
for i in range(trips_num):
time = yield Event(time, proc_num, 'pick up people')
time = yield Event(time, proc_num, 'drop off people')
yield Event(time, proc_num, 'go home')
# run
t1 = taxi_process(1, 1)
a = next(t1)
print(a) # Event(time=0, proc=1, action='leave garage')
b = t1.send(a.time + 6)
print(b) # Event(time=6, proc=1, action='pick up people')
c = t1.send(b.time + 12)
print(c) # Event(time=18, proc=1, action='drop off people')
d = t1.send(c.time + 1)
print(d) # Event(time=19, proc=1, action='go home')
模擬控制檯控制3個計程車非同步
import collections
import queue
import random
# time 欄位是事件發生時的模擬時間,
# proc 欄位是計程車程式例項的編號,
# action 欄位是描述活動的字串。
Event = collections.namedtuple('Event', 'time proc action')
def taxi_process(proc_num, trips_num, start_time=0):
"""
每次改變狀態時建立事件,把控制權讓給模擬器
:param proc_num:
:param trips_num:
:param start_time:
:return:
"""
time = yield Event(start_time, proc_num, 'leave garage')
for i in range(trips_num):
time = yield Event(time, proc_num, 'pick up people')
time = yield Event(time, proc_num, 'drop off people')
yield Event(time, proc_num, 'go home')
class SimulateTaxi(object):
"""
模擬計程車控制檯
"""
def __init__(self, proc_map):
# 儲存排定事件的 PriorityQueue 物件,
# 如果進來的是tuple型別,則預設使用tuple[0]做排序
self.events = queue.PriorityQueue()
# procs_map 引數是一個字典,使用dict構建本地副本
self.procs = dict(proc_map)
def run(self, end_time):
"""
排定並顯示事件,直到時間結束
:param end_time:
:return:
"""
for _, taxi_gen in self.procs.items():
leave_evt = next(taxi_gen)
self.events.put(leave_evt)
# 模擬系統的主迴圈
simulate_time = 0
while simulate_time < end_time:
if self.events.empty():
print('*** end of events ***')
break
# 第一個事件的發生
current_evt = self.events.get()
simulate_time, proc_num, action = current_evt
print('taxi:', proc_num, ', at time:', simulate_time, ', ', action)
# 準備下個事件的發生
proc_gen = self.procs[proc_num]
next_simulate_time = simulate_time + self.compute_duration()
try:
next_evt = proc_gen.send(next_simulate_time)
except StopIteration:
del self.procs[proc_num]
else:
self.events.put(next_evt)
else:
msg = '*** end of simulation time: {} events pending ***'
print(msg.format(self.events.qsize()))
@staticmethod
def compute_duration():
"""
隨機產生下個事件發生的時間
:return:
"""
duration_time = random.randint(1, 20)
return duration_time
# 生成3個計程車,現在全部都沒有離開garage
taxis = {i: taxi_process(i, (i + 1) * 2, i * 5)
for i in range(3)}
# 模擬執行
st = SimulateTaxi(taxis)
st.run(100)
相關文章
- python協程詳細解釋以及例子Python
- SQL中的case when then else end用法 【詳細】轉載SQL
- Python中協程(coroutine)詳解Python
- Python中threading的join和setDaemon的區別及用法[例子]Pythonthread
- python 協程用法總結(一)Python
- Python協程詳解Python
- Unity 協程(Coroutine)原理與用法詳解Unity
- Python3中*和**運算子的用法詳解!Python
- PHP中ZendCache用法的小例子PHP
- [pythonskill]Python中NaN和None的詳細比較PythonNaNNone
- C++【string】用法和例子C++
- C++【vector】用法和例子C++
- 【轉】Python之Numpy詳細教程Python
- 堆排序的Python實現(附詳細過程圖和講解)排序Python
- Python非同步協程(asyncio詳解)Python非同步
- python中gevent協程庫Python
- 【C#】-Dictionary的詳細用法C#
- String.format()的詳細用法ORM
- Java 中 this 和 super 的用法詳解Java
- Python入門課程—最詳細的Python庫介紹Python
- STL map 詳細用法
- Python:Django的ListView超詳細用法(含分頁paginate功能)PythonDjangoView
- Python的協程Python
- Python非同步IO程式設計之-asyncio協程應用例子Python非同步程式設計
- C++【stack/queue】用法和例子C++
- 詳述盒子模型(包含padding、border、margin的詳細用法和描述)模型padding
- mysql中的left join、right join 、inner join的詳細用法MySql
- 轉載golang中net/http包用法GolangHTTP
- Python中__init__的用法和理解Python
- Python | 詳解Python中的協程,為什麼說它的底層是生成器?Python
- python 協程與go協程的區別PythonGo
- Python協程與JavaScript協程的對比PythonJavaScript
- 轉:SVN中trunk,branches,tags用法詳解
- python 協程Python
- Python協程Python
- 超詳細講解頁面載入過程
- Python中dumps, loads dump, load用法詳解Python
- 【轉載】CL_HTTP_CLIENT的HTTP和SOAP用法示例HTTPclient