
Python協(xié)程的用法和例子詳解
從句法上看,協(xié)程與生成器類似,都是定義體中包含 yield 關(guān)鍵字的函數(shù)??墒牵趨f(xié)程中, yield 通常出現(xiàn)在表達式的右邊(例如, datum = yield),可以產(chǎn)出值,也可以不產(chǎn)出 —— 如果 yield 關(guān)鍵字后面沒有表達式,那么生成器產(chǎn)出 None。
協(xié)程可能會從調(diào)用方接收數(shù)據(jù),不過調(diào)用方把數(shù)據(jù)提供給協(xié)程使用的是 .send(datum) 方法,而不是next(…) 函數(shù)。
==yield 關(guān)鍵字甚至還可以不接收或傳出數(shù)據(jù)。不管數(shù)據(jù)如何流動, yield 都是一種流程控制工具,使用它可以實現(xiàn)協(xié)作式多任務:協(xié)程可以把控制器讓步給中心調(diào)度程序,從而激活其他的協(xié)程==。
協(xié)程的生成器的基本行為
這里有一個最簡單的協(xié)程代碼:
def simple_coroutine():
print('-> start')
x = yield
print('-> recived', x)
sc = simple_coroutine()
next(sc)
sc.send('zhexiao')
解釋:
1. 協(xié)程使用生成器函數(shù)定義:定義體中有 yield 關(guān)鍵字。
2. yield 在表達式中使用;如果協(xié)程只需從客戶那里接收數(shù)據(jù),那么產(chǎn)出的值是 None —— 這個值是隱式指定的,因為 yield 關(guān)鍵字右邊沒有表達式。
3. 首先要調(diào)用 next(…) 函數(shù),因為生成器還沒啟動,沒在 yield 語句處暫停,所以一開始無法發(fā)送數(shù)據(jù)。
4. 調(diào)用send方法,把值傳給 yield 的變量,然后協(xié)程恢復,繼續(xù)執(zhí)行下面的代碼,直到運行到下一個 yield 表達式,或者終止。
==注意:send方法只有當協(xié)程處于 GEN_SUSPENDED 狀態(tài)下時才會運作,所以我們使用 next() 方法激活協(xié)程到 yield 表達式處停止,或者我們也可以使用 sc.send(None),效果與 next(sc) 一樣==。
協(xié)程的四個狀態(tài):
協(xié)程可以身處四個狀態(tài)中的一個。當前狀態(tài)可以使用inspect.getgeneratorstate(…) 函數(shù)確定,該函數(shù)會返回下述字符串中的一個:
1. GEN_CREATED:等待開始執(zhí)行
2. GEN_RUNNING:解釋器正在執(zhí)行
3. GEN_SUSPENED:在yield表達式處暫停
4. GEN_CLOSED:執(zhí)行結(jié)束
==最先調(diào)用 next(sc) 函數(shù)這一步通常稱為“預激”(prime)協(xié)程==(即,讓協(xié)程向前執(zhí)行到第一個 yield 表達式,準備好作為活躍的協(xié)程使用)。
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
示例:使用協(xié)程計算移動平均值
def averager():
total = 0.0
count = 0
avg = None
while True:
num = yield avg
total += num
count += 1
avg = total/count
# run
ag = averager()
# 預激協(xié)程
print(next(ag)) # None
print(ag.send(10)) # 10
print(ag.send(20)) # 15
解釋:
1. 調(diào)用 next(ag) 函數(shù)后,協(xié)程會向前執(zhí)行到 yield 表達式,產(chǎn)出 average 變量的初始值——None。
2. 此時,協(xié)程在 yield 表達式處暫停。
3. 使用 send() 激活協(xié)程,把發(fā)送的值賦給 num,并計算出 avg 的值。
4. 使用 print 打印出 yield 返回的數(shù)據(jù)。
終止協(xié)程和異常處理
協(xié)程中未處理的異常會向上冒泡,傳給 next 函數(shù)或 send 方法的調(diào)用方(即觸發(fā)協(xié)程的對象)。
==終止協(xié)程的一種方式:發(fā)送某個哨符值,讓協(xié)程退出。內(nèi)置的 None 和Ellipsis 等常量經(jīng)常用作哨符值==。
顯式地把異常發(fā)給協(xié)程
從 Python 2.5 開始,客戶代碼可以在生成器對象上調(diào)用兩個方法,顯式地把異常發(fā)給協(xié)程。
generator.throw(exc_type[, exc_value[, traceback]])
致使生成器在暫停的 yield 表達式處拋出指定的異常。如果生成器處理了拋出的異常,代碼會向前執(zhí)行到下一個 yield 表達式,而產(chǎn)出的值會成為調(diào)用 generator.throw方法得到的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調(diào)用方的上下文中。
generator.close()
致使生成器在暫停的 yield 表達式處拋出 GeneratorExit 異常。如果生成器沒有處理這個異常,或者拋出了 StopIteration 異常(通常是指運行到結(jié)尾),調(diào)用方不會報錯。如果收到 GeneratorExit 異常,生成器一定不能產(chǎn)出值,否則解釋器會拋出RuntimeError 異常。生成器拋出的其他異常會向上冒泡,傳給調(diào)用方。
異常處理示例:
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()
如果傳入無法處理的異常,則協(xié)程會終止:
he.throw(Exception) # run demo exception
yield from獲取協(xié)程的返回值
為了得到返回值,協(xié)程必須正常終止;然后生成器對象會拋出StopIteration 異常,異常對象的 value 屬性保存著返回的值。
==yield from 結(jié)構(gòu)會在內(nèi)部自動捕獲 StopIteration 異常==。對 yield from 結(jié)構(gòu)來說,解釋器不僅會捕獲 StopIteration 異常,還會把value 屬性的值變成 yield from 表達式的值。
yield from基本用法
==在生成器 gen 中使用 yield from subgen() 時, subgen 會獲得控制權(quán),把產(chǎn)出的值傳給 gen 的調(diào)用方,即調(diào)用方可以直接控制 subgen。與此同時, gen 會阻塞,等待 subgen 終止==。
下面2個函數(shù)的作用一樣,只是使用了 yield from 的更加簡潔:
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 對象所做的第一件事是,調(diào)用 iter(x),從中獲取迭代器,因此, x 可以是任何可迭代的對象,這只是 yield from 最基礎的用法==。
yield from高級用法
==yield from 的主要功能是打開雙向通道,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來,這樣二者可以直接發(fā)送和產(chǎn)出值,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量處理異常的樣板代碼==。
yield from 專門的術(shù)語
委派生成器:包含 yield from 表達式的生成器函數(shù)。
子生成器:從 yield from 中 部分獲取的生成器。
圖示
解釋:
1. 委派生成器在 yield from 表達式處暫停時,調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器。
2. 子生成器再把產(chǎn)出的值發(fā)給調(diào)用方。
3. 子生成器返回之后,解釋器會拋出 StopIteration 異常,并把返回值附加到異常對象上,此時委派生成器會恢復。
高級示例
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():
# 獲得協(xié)程
coroutine = grouper(storages, k)
# 預激協(xié)程
next(coroutine)
# 發(fā)送數(shù)據(jù)到協(xié)程
for dt in v:
coroutine.send(dt)
# 終止協(xié)程
coroutine.send(None)
print(storages)
# run
client()
解釋:
1. 外層 for 循環(huán)每次迭代會新建一個 grouper 實例,賦值給 coroutine 變量; grouper 是委派生成器。
2. 調(diào)用 next(coroutine),預激委派生成器 grouper,此時進入 while True 循環(huán),調(diào)用子生成器 averager 后,在 yield from 表達式處暫停。
3. 內(nèi)層 for 循環(huán)調(diào)用 coroutine.send(value),直接把值傳給子生成器 averager。同時,當前的 grouper 實例(coroutine)在 yield from 表達式處暫停。
4. 內(nèi)層循環(huán)結(jié)束后, grouper 實例依舊在 yield from 表達式處暫停,因此, grouper函數(shù)定義體中為 results[key] 賦值的語句還沒有執(zhí)行。
5. coroutine.send(None) 終止 averager 子生成器,子生成器拋出 StopIteration 異常并將返回的數(shù)據(jù)包含在異常對象的value中,yield from 可以直接抓取 StopItration 異常并將異常對象的 value 賦值給 results[key]
yield from的意義
子生成器產(chǎn)出的值都直接傳給委派生成器的調(diào)用方(即客戶端代碼)。
使用 send() 方法發(fā)給委派生成器的值都直接傳給子生成器。如果發(fā)送的值是None,那么會調(diào)用子生成器的 next() 方法。如果發(fā)送的值不是 None,那么會調(diào)用子生成器的 send() 方法。如果調(diào)用的方法拋出 StopIteration 異常,那么委派生成器恢復運行。任何其他異常都會向上冒泡,傳給委派生成器。
生成器退出時,生成器(或子生成器)中的 return expr 表達式會觸發(fā) StopIteration(expr) 異常拋出。
yield from 表達式的值是子生成器終止時傳給 StopIteration 異常的第一個參數(shù)。
傳入委派生成器的異常,除了 GeneratorExit 之外都傳給子生成器的 throw() 方法。如果調(diào)用 throw() 方法時拋出 StopIteration 異常,委派生成器恢復運行。 StopIteration 之外的異常會向上冒泡,傳給委派生成器。
如果把 GeneratorExit 異常傳入委派生成器,或者在委派生成器上調(diào)用 close() 方法,那么在子生成器上調(diào)用 close() 方法,如果它有的話。如果調(diào)用close()方法導致異常拋出,那么異常會向上冒泡,傳給委派生成器;否則,委派生成器拋出GeneratorExit 異常。
使用案例
協(xié)程能自然地表述很多算法,例如仿真、游戲、異步 I/O,以及其他事件驅(qū)動型編程形式或協(xié)作式多任務。協(xié)程是 asyncio 包的基礎構(gòu)建。通過仿真系統(tǒng)能說明如何使用協(xié)程代替線程實現(xiàn)并發(fā)的活動。
在仿真領域,進程這個術(shù)語指代模型中某個實體的活動,與操作系統(tǒng)中的進程無關(guān)。仿真系統(tǒng)中的一個進程可以使用操作系統(tǒng)中的一個進程實現(xiàn),但是通常會使用一個線程或一個協(xié)程實現(xiàn)。
出租車示例
import collections
# time 字段是事件發(fā)生時的仿真時間,
# proc 字段是出租車進程實例的編號,
# action 字段是描述活動的字符串。
Event = collections.namedtuple('Event', 'time proc action')
def taxi_process(proc_num, trips_num, start_time=0):
"""
每次改變狀態(tài)時創(chuàng)建事件,把控制權(quán)讓給仿真器
: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 字段是事件發(fā)生時的仿真時間,
# proc 字段是出租車進程實例的編號,
# action 字段是描述活動的字符串。
Event = collections.namedtuple('Event', 'time proc action')
def taxi_process(proc_num, trips_num, start_time=0):
"""
每次改變狀態(tài)時創(chuàng)建事件,把控制權(quán)讓給仿真器
: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 參數(shù)是一個字典,使用dict構(gòu)建本地副本
self.procs = dict(proc_map)
def run(self, end_time):
"""
排定并顯示事件,直到時間結(jié)束
:param end_time:
:return:
"""
for _, taxi_gen in self.procs.items():
leave_evt = next(taxi_gen)
self.events.put(leave_evt)
# 仿真系統(tǒng)的主循環(huán)
simulate_time = 0
while simulate_time < end_time:
if self.events.empty():
print('*** end of events ***')
break
# 第一個事件的發(fā)生
current_evt = self.events.get()
simulate_time, proc_num, action = current_evt
print('taxi:', proc_num, ', at time:', simulate_time, ', ', action)
# 準備下個事件的發(fā)生
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():
"""
隨機產(chǎn)生下個事件發(fā)生的時間
:return:
"""
duration_time = random.randint(1, 20)
return duration_time
# 生成3個出租車,現(xiàn)在全部都沒有離開garage
taxis = {i: taxi_process(i, (i + 1) * 2, i * 5)
for i in range(3)}
# 模擬運行
st = SimulateTaxi(taxis)
st.run(100)
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
LSTM 模型輸入長度選擇技巧:提升序列建模效能的關(guān)鍵? 在循環(huán)神經(jīng)網(wǎng)絡(RNN)家族中,長短期記憶網(wǎng)絡(LSTM)憑借其解決長序列 ...
2025-07-11CDA 數(shù)據(jù)分析師報考條件詳解與準備指南? ? 在數(shù)據(jù)驅(qū)動決策的時代浪潮下,CDA 數(shù)據(jù)分析師認證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計的實用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實施重大更新。 此次更新旨在確保認 ...
2025-07-10BI 大數(shù)據(jù)分析師:連接數(shù)據(jù)與業(yè)務的價值轉(zhuǎn)化者? ? 在大數(shù)據(jù)與商業(yè)智能(Business Intelligence,簡稱 BI)深度融合的時代,BI ...
2025-07-10SQL 在預測分析中的應用:從數(shù)據(jù)查詢到趨勢預判? ? 在數(shù)據(jù)驅(qū)動決策的時代,預測分析作為挖掘數(shù)據(jù)潛在價值的核心手段,正被廣泛 ...
2025-07-10數(shù)據(jù)查詢結(jié)束后:分析師的收尾工作與價值深化? ? 在數(shù)據(jù)分析的全流程中,“query end”(查詢結(jié)束)并非工作的終點,而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報考到取證的全攻略? 在數(shù)字經(jīng)濟蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢性檢驗:捕捉數(shù)據(jù)背后的時間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢性檢驗如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時間維度的精準切片? ? 在數(shù)據(jù)的世界里,時間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準 ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應用與實戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗:數(shù)據(jù)趨勢與突變分析的有力工具? ? ? 在數(shù)據(jù)分析的廣袤領域中,準確捕捉數(shù)據(jù)的趨勢變化以及識別 ...
2025-07-08備戰(zhàn) CDA 數(shù)據(jù)分析師考試:需要多久?如何規(guī)劃? CDA(Certified Data Analyst)數(shù)據(jù)分析師認證作為國內(nèi)權(quán)威的數(shù)據(jù)分析能力認證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應對策略? 長短期記憶網(wǎng)絡(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(RNN)的一種變體,憑借獨特的門控機制,在 ...
2025-07-07統(tǒng)計學方法在市場調(diào)研數(shù)據(jù)中的深度應用? 市場調(diào)研是企業(yè)洞察市場動態(tài)、了解消費者需求的重要途徑,而統(tǒng)計學方法則是市場調(diào)研數(shù) ...
2025-07-07CDA數(shù)據(jù)分析師證書考試全攻略? 在數(shù)字化浪潮席卷全球的當下,數(shù)據(jù)已成為企業(yè)決策、行業(yè)發(fā)展的核心驅(qū)動力,數(shù)據(jù)分析師也因此成為 ...
2025-07-07剖析 CDA 數(shù)據(jù)分析師考試題型:解鎖高效備考與答題策略? CDA(Certified Data Analyst)數(shù)據(jù)分析師考試作為衡量數(shù)據(jù)專業(yè)能力的 ...
2025-07-04SQL Server 字符串截取轉(zhuǎn)日期:解鎖數(shù)據(jù)處理的關(guān)鍵技能? 在數(shù)據(jù)處理與分析工作中,數(shù)據(jù)格式的規(guī)范性是保證后續(xù)分析準確性的基礎 ...
2025-07-04CDA 數(shù)據(jù)分析師視角:從數(shù)據(jù)迷霧中探尋商業(yè)真相? 在數(shù)字化浪潮席卷全球的今天,數(shù)據(jù)已成為企業(yè)決策的核心驅(qū)動力,CDA(Certifie ...
2025-07-04CDA 數(shù)據(jù)分析師:開啟數(shù)據(jù)職業(yè)發(fā)展新征程? ? 在數(shù)據(jù)成為核心生產(chǎn)要素的今天,數(shù)據(jù)分析師的職業(yè)價值愈發(fā)凸顯。CDA(Certified D ...
2025-07-03