<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
字典為動詞“to yield”給出了兩個釋義:產出和讓步。對於 Python 生成器中的 yield 來說,這兩個含義都成立。yield item 這行程式碼會產出一個值,提供給 next(...) 的呼叫方;此外,還會作出讓步,暫停執行生成器,讓呼叫方繼續工作,直到需要使用另一個值時再呼叫 next()。呼叫方會從生成器中拉取值。
從句法上看,協程與生成器類似,都是定義體中包含 yield 關鍵字的函數。可是,在協程中,yield 通常出現在表示式的右邊(例如,datum = yield),可以產出值,也可以不產出----yield 關鍵字後面沒有表示式。協程可能會從呼叫方接收資料,呼叫方使用 .send(datum) 方法把資料提供給協程。
自python中加入yield關鍵字後,又經過了一系列的演化:
yield 關鍵字可以在表示式中使用(a = yield b);
生成器 API 中增加了.send(value) 方法(生成器的呼叫方可以使用 .send(...) 方法傳送資料,傳送的資料會成為生成器函數中 yield 表示式的值);
PEP 342 新增了 .throw(...) 和 .close() 方法(前者的作用是讓呼叫方丟擲異常,在生成器中處理;後者的作用是終止生成器);
因此,生成器可以作為協程使用。協程是指一個過程,這個過程與呼叫方共同作業,產出由呼叫方提供的值。
協程最近的演進來自 Python 3.3實現的“PEP 380—Syntax for Delegating to a Subgenerator”(https://www.python.org/dev/peps/pep-0380/)。PEP 380 對生成器函數的句法做了兩處改動:
生成器可以返回一個值;以前如果在生成器中給 return 語句提供值,會丟擲 SyntaxError 異常;
新引入了 yield from 句法,使用它可以把複雜的生成器重構成小型的巢狀生成器,省去了之前把生成器的工作委託給子生成器所需的大量樣板程式碼。
協程可以身處四個狀態中的一個。當前狀態可以使用inspect.getgeneratorstate(...) 函數確定,該函數會返回下述字串中的一個。
GEN_CREATED:等待開始執行;
GEN_RUNNING:直譯器正在執行(只有在多執行緒應用中才能看到這個狀態);
GEN_SUSPENDED:在 yield 表示式處暫停;
GEN_CLOSED:執行結束;
一個簡單的例子如下;
>>> def simple_coro2(a): ... print('-> Started: a =', a) ... b = yield a ... print('-> Received: b =', b) ... c = yield a + b ... print('-> Received: c =', c) ... >>> my_coro2 = simple_coro2(14) >>> from inspect import getgeneratorstate >>> getgeneratorstate(my_coro2) 'GEN_CREATED' >>> next(my_coro2) -> Started: a = 14 14 >>> getgeneratorstate(my_coro2) 'GEN_SUSPENDED' >>> my_coro2.send(28) -> Received: b = 28 42 >>> my_coro2.send(99) -> Received: c = 99 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> getgeneratorstate(my_coro2) 'GEN_CLOSED'
最先呼叫 next(my_coro2) 函數這一步通常稱為“預激”(prime)協程(即,讓協程向前執行到第一個 yield 表示式,準備好作為活躍的協程使用)。
關鍵的一點是,協程在 yield 關鍵字所在的位置暫停執行。在賦值語句中,=右邊的程式碼在賦值之前執行。因此,對於 b = yield a 這行程式碼來說,等到使用者端程式碼再啟用協程時才會設定 b 的值。
simple_coro2 協程的執行過程分為 3 個階段,如下圖所示:
下面是一個計算移動平均值的協程:
def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count >>> coro_avg = averager() >>> next(coro_avg) #呼叫 next 函數,預激協程 >>> coro_avg.send(10) 10.0 >>> coro_avg.send(30) 20.0 >>> coro_avg.send(5) 15.0
這個無限迴圈表明,只要呼叫方不斷把值發給這個協程,它就會一直接收值,然後生成結果。僅當呼叫方在協程上呼叫 .close() 方法,或者沒有對協程的參照而被垃圾回收程式回收時,這個協程才會終止。
呼叫 next(coro_avg) 函數後,協程會向前執行到yield 表示式,產出 average 變數的初始值——None,因此不會出現在控制檯中。此時,協程在 yield 表示式處暫停,等到呼叫方傳送值。coro_avg.send(10) 那一行傳送一個值,啟用協程,把傳送的值賦給 term,並更新 total、count 和 average 三個變數的值,然後開始 while 迴圈的下一次迭代,產出 average 變數的值,等待下一次為term 變數賦值。
如果不預激,那麼協程沒什麼用。呼叫 my_coro.send(x) 之前,記住一定要呼叫next(my_coro)。為了簡化協程的用法,有時會使用一個預激裝飾器。
下面就是一個預激裝飾器的例子(Python3):
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 averager2(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count >>> coro_avg = averager() >>> from inspect import getgeneratorstate >>> getgeneratorstate(coro_avg) 'GEN_SUSPENDED' >>> coro_avg.send(10) 10.0 >>> coro_avg.send(30) 20.0 >>> coro_avg.send(5) 15.0
注意,使用 yield from 句法呼叫協程時,會自動預激。
協程中未處理的異常會向上冒泡,傳給 next 函數或 send 方法的呼叫方(即觸發協程的物件)。
>>> from coroaverager1 import averager >>> coro_avg = averager() >>> coro_avg.send(40) 40.0 >>> coro_avg.send(50) 45.0 >>> coro_avg.send('spam') Traceback (most recent call last): ... TypeError: unsupported operand type(s) for +=: 'float' and 'str' >>> coro_avg.send(60) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
由於在協程內沒有處理異常,協程會終止。如果試圖重新啟用協程,會丟擲StopIteration 異常。
從 Python 2.5 開始,客戶程式碼可以在生成器物件上呼叫兩個方法:throw 和 close,顯式地把異常發給協程。
1:generator.throw(exc_type[, exc_value[, traceback]])
使生成器在暫停的 yield 表示式處丟擲指定的異常。如果生成器處理了丟擲的異常,程式碼會向前執行到下一個 yield 表示式,而產出的值會成為呼叫 generator.throw方法得到的返回值。如果生成器沒有處理丟擲的異常,異常會向上冒泡,傳到呼叫方的上下文中。
2:generator.close()
使生成器在暫停的 yield 表示式處丟擲 GeneratorExit 異常。如果生成器沒有處理這個異常,或者丟擲了 StopIteration 異常(通常是指執行到結尾),呼叫方不會報錯。如果收到 GeneratorExit 異常,生成器一定不能產出值,否則直譯器會丟擲RuntimeError 異常。生成器丟擲的其他異常會向上冒泡,傳給呼叫方。
範例如下:
class DemoException(Exception): """為這次演示定義的異常型別。""" def demo_exc_handling(): print('-> coroutine started') while True: try: x = yield except DemoException: print('*** DemoException handled. Continuing...') else: print('-> coroutine received: {!r}'.format(x)) raise RuntimeError('This line should never run.') >>> exc_coro = demo_exc_handling() >>> next(exc_coro) -> coroutine started >>> exc_coro.send(11) -> coroutine received: 11 >>> exc_coro.send(22) -> coroutine received: 22 >>> exc_coro.throw(DemoException) *** DemoException handled. Continuing... >>> getgeneratorstate(exc_coro) 'GEN_SUSPENDED' >>> exc_coro.close() >>> from inspect import getgeneratorstate >>> getgeneratorstate(exc_coro) 'GEN_CLOSED'
在Python2中,生成器函數中的return不允許返回附帶返回值。在Python3中取消了這一限制,因而允許協程可以返回值:
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(6.5) >>> coro_avg.send(None) Traceback (most recent call last): ... StopIteration: Result(count=3, average=15.5)
傳送 None 會終止迴圈,導致協程結束,返回結果。一如既往,生成器物件會丟擲StopIteration 異常。異常物件的 value 屬性儲存著返回的值。
注意,return 表示式的值會偷偷傳給呼叫方,賦值給 StopIteration 異常的一個屬性。這樣做有點不合常理,但是能保留生成器物件的常規行為——耗盡時丟擲StopIteration 異常。如果需要接收返回值,可以這樣:
>>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result Result(count=3, average=15.5)
獲取協程的返回值要繞個圈子,可以使用Python3.3引入的yield from獲取返回值。yield from 結構會在內部自動捕獲 StopIteration 異常。這種處理方式與 for 迴圈處理 StopIteration 異常的方式一樣。對 yield from 結構來說,直譯器不僅會捕獲 StopIteration 異常,還會把value 屬性的值變成 yield from 表示式的值。
yield from 是 Python3.3 後新加的語言結構。在其他語言中,類似的結構使用 await 關鍵字,這個名稱好多了,因為它傳達了至關重要的一點:在生成器 gen 中使用 yield from subgen() 時,subgen 會獲得控制權,把產出的值傳給 gen 的呼叫方,即呼叫方可以直接控制 subgen。與此同時,gen 會阻塞,等待 subgen 終止。
yield from 可用於簡化 for 迴圈中的 yield 表示式。例如:
>>> def gen(): ... for c in 'AB': ... yield c ... for i in range(1, 3): ... yield i ... >>> list(gen()) ['A', 'B', 1, 2]
可以改為
>>> def gen(): ... yield from 'AB' ... yield from range(1, 3) ... >>> list(gen()) ['A', 'B', 1, 2]
yield from x 表示式對 x 物件所做的第一件事是,呼叫 iter(x),從中獲取迭代器。因此,x 可以是任何可迭代的物件。
如果 yield from 結構唯一的作用是替代產出值的巢狀 for 迴圈,這個結構很有可能不會新增到 Python 語言中。
yield from 的主要功能是開啟雙向通道,把最外層的呼叫方與最內層的子生成器連線起來,這樣二者可以直接傳送和產出值,還可以直接傳入異常,而不用在位於中間的協程中新增大量處理異常的樣板程式碼。有了這個結構,協程可以通過以前不可能的方式委託職責。
PEP 380 使用了一些yield from使用的專門術語:
委派生成器:包含 yield from <iterable> 表示式的生成器函數;
子生成器:從 yield from 表示式中 <iterable> 部分獲取的生成器;
呼叫方:呼叫委派生成器的使用者端程式碼;
下圖是這三者之間的互動關係:
委派生成器在 yield from 表示式處暫停時,呼叫方可以直接把資料發給子生成器,子生成器再把產出的值發給呼叫方。子生成器返回之後,直譯器會丟擲StopIteration 異常,並把返回值附加到異常物件上,此時委派生成器會恢復。
下面是一個求平均身高和體重的範例程式碼:
from collections import namedtuple Result = namedtuple('Result', 'count average') # 子生成器 def averager(): total = 0.0 count = 0 average = None while True: # main 函數傳送資料到這裡 print("in averager, before yield") term = yield if term is None: # 終止條件 break total += term count += 1 average = total/count print("in averager, return result") return Result(count, average) # 返回的Result 會成為grouper函數中yield from表示式的值 # 委派生成器 def grouper(results, key): # 這個迴圈每次都會新建一個averager 範例,每個範例都是作為協程使用的生成器物件 while True: print("in grouper, before yield from averager, key is ", key) results[key] = yield from averager() print("in grouper, after yield from, key is ", key) # 呼叫方 def main(data): results = {} for key, values in data.items(): # group 是呼叫grouper函數得到的生成器物件 group = grouper(results, key) print("ncreate group: ", group) next(group) #預激 group 協程。 print("pre active group ok") for value in values: # 把各個value傳給grouper 傳入的值最終到達averager函數中; # grouper並不知道傳入的是什麼,同時grouper範例在yield from處暫停 print("send to %r value %f now"%(group, value)) group.send(value) # 把None傳入groupper,傳入的值最終到達averager函數中,導致當前範例終止。然後繼續建立下一個範例。 # 如果沒有group.send(None),那麼averager子生成器永遠不會終止,委派生成器也永遠不會在此啟用,也就不會為result[key]賦值 print("send to %r none"%group) group.send(None) print("report result: ") report(results) # 輸出報告 def report(results): for key, result in sorted(results.items()): group, unit = key.split(';') print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit)) data = { 'girls;kg':[40, 41, 42, 43, 44, 54], 'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6], 'boys;kg':[50, 51, 62, 53, 54, 54], 'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6], } if __name__ == '__main__': main(data)
grouper 傳送的每個值都會經由 yield from 處理,通過管道傳給 averager 範例。grouper 會在 yield from 表示式處暫停,等待 averager 範例處理使用者端發來的值。averager 範例執行完畢後,返回的值繫結到 results[key] 上。while 迴圈會不斷建立 averager 範例,處理更多的值。
外層 for 迴圈重新迭代時會新建一個 grouper 範例,然後繫結到 group 變數上。前一個 grouper 範例(以及它建立的尚未終止的 averager 子生成器範例)被垃圾回收程式回收。
程式碼結果如下:
create group: <generator object grouper at 0x7f34ce8458e0> in grouper, before yield from averager, key is girls;kg in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce8458e0> value 40.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 41.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 42.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 43.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 44.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 54.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> none in averager, return result in grouper, after yield from, key is girls;kg in grouper, before yield from averager, key is girls;kg in averager, before yield create group: <generator object grouper at 0x7f34ce845678> in grouper, before yield from averager, key is girls;m in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce845678> value 1.500000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.800000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.500000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.450000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> none in averager, return result in grouper, after yield from, key is girls;m in grouper, before yield from averager, key is girls;m in averager, before yield create group: <generator object grouper at 0x7f34ce845620> in grouper, before yield from averager, key is boys;kg in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce845620> value 50.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 51.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 62.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 53.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 54.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 54.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> none in averager, return result in grouper, after yield from, key is boys;kg in grouper, before yield from averager, key is boys;kg in averager, before yield create group: <generator object grouper at 0x7f34ce8458e0> in grouper, before yield from averager, key is boys;m in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.700000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.550000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> none in averager, return result in grouper, after yield from, key is boys;m in grouper, before yield from averager, key is boys;m in averager, before yield report result: 6 boys averaging 54.00kg 6 boys averaging 1.68m 6 girls averaging 44.00kg 6 girls averaging 1.58m
這個試驗想表明的關鍵一點是,如果子生成器不終止,委派生成器會在yield from 表示式處永遠暫停。如果是這樣,程式不會向前執行,因為 yield from(與 yield 一樣)把控制權轉交給客戶程式碼(即,委派生成器的呼叫方)了。
把迭代器當作生成器使用,相當於把子生成器的定義體內聯在 yield from 表示式中。此外,子生成器可以執行 return 語句,返回一個值,而返回的值會成為 yield from 表示式的值。
PEP 380 在“Proposal”一節(https://www.python.org/dev/peps/pep-0380/#proposal)分六點說明了 yield from 的行為。這裡幾乎原封不動地引述,不過把有歧義的“迭代器”一詞都換成了“子生成器”,還做了進一步說明。上面的範例闡明瞭下述四點:
子生成器產出的值都直接傳給委派生成器的呼叫方(即使用者端程式碼);
使用 send() 方法發給委派生成器的值都直接傳給子生成器。如果傳送的值是None,那麼會呼叫子生成器的 __next__() 方法。如果傳送的值不是 None,那麼會呼叫子生成器的 send() 方法。如果子生成器丟擲 StopIteration 異常,那麼委派生成器恢復執行。任何其他異常都會向上冒泡,傳給委派生成器;
生成器退出時,生成器(或子生成器)中的 return expr 表示式會觸發StopIteration(expr) 異常丟擲;
yield from 表示式的值是子生成器終止時傳給 StopIteration 異常的第一個引數。
yield from 的具體語意很難理解,尤其是處理異常的那兩點。在PEP 380 中闡述了 yield from 的語意。還使用虛擬碼(使用 Python 句法)演示了 yield from 的行為。
若想研究那段虛擬碼,最好將其簡化,只涵蓋 yield from 最基本且最常見的用法:yield from 出現在委派生成器中,使用者端程式碼驅動著委派生成器,而委派生成器驅動著子生成器。為了簡化涉及到的邏輯,假設使用者端沒有在委派生成器上呼叫throw(...) 或 close() 方法。而且假設子生成器不會丟擲異常,而是一直執行到終止,讓直譯器丟擲 StopIteration 異常。上面範例中的指令碼就做了這些簡化邏輯的假設。
下面的虛擬碼,等效於委派生成器中的 RESULT = yield from EXPR 語句(這裡針對的是最簡單的情況:不支援 .throw(...) 和 .close() 方法,而且只處理 StopIteration 異常):
_i = iter(EXPR) try: _y = next(_i) except StopIteration as _e: _r = _e.value else: while 1: _s = yield _y try: _y = _i.send(_s) except StopIteration as _e: _r = _e.value break RESULT = _r
但是,現實情況要複雜一些,因為要處理客戶對 throw(...) 和 close() 方法的呼叫,而這兩個方法執行的操作必須傳入子生成器。此外,子生成器可能只是純粹的迭代器,不支援 throw(...) 和 close() 方法,因此 yield from 結構的邏輯必須處理這種情況。如果子生成器實現了這兩個方法,而在子生成器內部,這兩個方法都會觸發異常丟擲,這種情況也必須由 yield from 機制處理。呼叫方可能會無緣無故地讓子生成器自己丟擲異常,實現 yield from 結構時也必須處理這種情況。最後,為了優化,如果呼叫方呼叫 next(...) 函數或 .send(None) 方法,都要轉交職責,在子生成器上呼叫next(...) 函數;僅當呼叫方傳送的值不是 None 時,才使用子生成器的 .send(...) 方法。
下面的虛擬碼,是考慮了上述情況之後,語句:RESULT = yield from EXPR的等效程式碼:
_i = iter(EXPR) try: _y = next(_i) except StopIteration as _e: _r = _e.value else: while 1: try: _s = yield _y except GeneratorExit as _e: try: _m = _i.close except AttributeError: pass else: _m() raise _e except BaseException as _e: _x = sys.exc_info() try: _m = _i.throw except AttributeError: raise _e else: try: _y = _m(*_x) except StopIteration as _e: _r = _e.value break else: try: if _s is None: _y = next(_i) else: _y = _i.send(_s) except StopIteration as _e: _r = _e.value break RESULT = _r
上面的虛擬碼中,會預激子生成器。這表明,用於自動預激的裝飾器與 yield from 結構不相容。
到此這篇關於python協程之yield和yield from的文章就介紹到這了,更多相關python協程yield和yield from內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45