python協(xié)程之yield和yield?from實(shí)例詳解
前言
字典為動(dòng)詞“to yield”給出了兩個(gè)釋義:產(chǎn)出和讓步。對(duì)于 Python 生成器中的 yield 來(lái)說(shuō),這兩個(gè)含義都成立。yield item 這行代碼會(huì)產(chǎn)出一個(gè)值,提供給 next(...) 的調(diào)用方;此外,還會(huì)作出讓步,暫停執(zhí)行生成器,讓調(diào)用方繼續(xù)工作,直到需要使用另一個(gè)值時(shí)再調(diào)用 next()。調(diào)用方會(huì)從生成器中拉取值。
從句法上看,協(xié)程與生成器類(lèi)似,都是定義體中包含 yield 關(guān)鍵字的函數(shù)??墒?,在協(xié)程中,yield 通常出現(xiàn)在表達(dá)式的右邊(例如,datum = yield),可以產(chǎn)出值,也可以不產(chǎn)出----yield 關(guān)鍵字后面沒(méi)有表達(dá)式。協(xié)程可能會(huì)從調(diào)用方接收數(shù)據(jù),調(diào)用方使用 .send(datum) 方法把數(shù)據(jù)提供給協(xié)程。
一:生成器如何進(jìn)化成協(xié)程
自python中加入yield關(guān)鍵字后,又經(jīng)過(guò)了一系列的演化:
yield 關(guān)鍵字可以在表達(dá)式中使用(a = yield b);
生成器 API 中增加了.send(value) 方法(生成器的調(diào)用方可以使用 .send(...) 方法發(fā)送數(shù)據(jù),發(fā)送的數(shù)據(jù)會(huì)成為生成器函數(shù)中 yield 表達(dá)式的值);
PEP 342 添加了 .throw(...) 和 .close() 方法(前者的作用是讓調(diào)用方拋出異常,在生成器中處理;后者的作用是終止生成器);
因此,生成器可以作為協(xié)程使用。協(xié)程是指一個(gè)過(guò)程,這個(gè)過(guò)程與調(diào)用方協(xié)作,產(chǎn)出由調(diào)用方提供的值。
協(xié)程最近的演進(jìn)來(lái)自 Python 3.3實(shí)現(xiàn)的“PEP 380—Syntax for Delegating to a Subgenerator”(https://www.python.org/dev/peps/pep-0380/)。PEP 380 對(duì)生成器函數(shù)的句法做了兩處改動(dòng):
生成器可以返回一個(gè)值;以前如果在生成器中給 return 語(yǔ)句提供值,會(huì)拋出 SyntaxError 異常;
新引入了 yield from 句法,使用它可以把復(fù)雜的生成器重構(gòu)成小型的嵌套生成器,省去了之前把生成器的工作委托給子生成器所需的大量樣板代碼。
二:用作協(xié)程的生成器的基本行為
協(xié)程可以身處四個(gè)狀態(tài)中的一個(gè)。當(dāng)前狀態(tài)可以使用inspect.getgeneratorstate(...) 函數(shù)確定,該函數(shù)會(huì)返回下述字符串中的一個(gè)。
GEN_CREATED:等待開(kāi)始執(zhí)行;
GEN_RUNNING:解釋器正在執(zhí)行(只有在多線(xiàn)程應(yīng)用中才能看到這個(gè)狀態(tài));
GEN_SUSPENDED:在 yield 表達(dá)式處暫停;
GEN_CLOSED:執(zhí)行結(jié)束;
一個(gè)簡(jiǎn)單的例子如下;
>>> 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' 最先調(diào)用 next(my_coro2) 函數(shù)這一步通常稱(chēng)為“預(yù)激”(prime)協(xié)程(即,讓協(xié)程向前執(zhí)行到第一個(gè) yield 表達(dá)式,準(zhǔn)備好作為活躍的協(xié)程使用)。
關(guān)鍵的一點(diǎn)是,協(xié)程在 yield 關(guān)鍵字所在的位置暫停執(zhí)行。在賦值語(yǔ)句中,=右邊的代碼在賦值之前執(zhí)行。因此,對(duì)于 b = yield a 這行代碼來(lái)說(shuō),等到客戶(hù)端代碼再激活協(xié)程時(shí)才會(huì)設(shè)定 b 的值。
simple_coro2 協(xié)程的執(zhí)行過(guò)程分為 3 個(gè)階段,如下圖所示:

三:使用協(xié)程計(jì)算移動(dòng)平均值
下面是一個(gè)計(jì)算移動(dòng)平均值的協(xié)程:
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) #調(diào)用 next 函數(shù),預(yù)激協(xié)程
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0這個(gè)無(wú)限循環(huán)表明,只要調(diào)用方不斷把值發(fā)給這個(gè)協(xié)程,它就會(huì)一直接收值,然后生成結(jié)果。僅當(dāng)調(diào)用方在協(xié)程上調(diào)用 .close() 方法,或者沒(méi)有對(duì)協(xié)程的引用而被垃圾回收程序回收時(shí),這個(gè)協(xié)程才會(huì)終止。
調(diào)用 next(coro_avg) 函數(shù)后,協(xié)程會(huì)向前執(zhí)行到y(tǒng)ield 表達(dá)式,產(chǎn)出 average 變量的初始值——None,因此不會(huì)出現(xiàn)在控制臺(tái)中。此時(shí),協(xié)程在 yield 表達(dá)式處暫停,等到調(diào)用方發(fā)送值。coro_avg.send(10) 那一行發(fā)送一個(gè)值,激活協(xié)程,把發(fā)送的值賦給 term,并更新 total、count 和 average 三個(gè)變量的值,然后開(kāi)始 while 循環(huán)的下一次迭代,產(chǎn)出 average 變量的值,等待下一次為term 變量賦值。
四:預(yù)激協(xié)程的裝飾器
如果不預(yù)激,那么協(xié)程沒(méi)什么用。調(diào)用 my_coro.send(x) 之前,記住一定要調(diào)用next(my_coro)。為了簡(jiǎn)化協(xié)程的用法,有時(shí)會(huì)使用一個(gè)預(yù)激裝飾器。
下面就是一個(gè)預(yù)激裝飾器的例子(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 句法調(diào)用協(xié)程時(shí),會(huì)自動(dòng)預(yù)激。
五:終止協(xié)程和異常處理
協(xié)程中未處理的異常會(huì)向上冒泡,傳給 next 函數(shù)或 send 方法的調(diào)用方(即觸發(fā)協(xié)程的對(duì)象)。
>>> 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由于在協(xié)程內(nèi)沒(méi)有處理異常,協(xié)程會(huì)終止。如果試圖重新激活協(xié)程,會(huì)拋出StopIteration 異常。
從 Python 2.5 開(kāi)始,客戶(hù)代碼可以在生成器對(duì)象上調(diào)用兩個(gè)方法:throw 和 close,顯式地把異常發(fā)給協(xié)程。
1:generator.throw(exc_type[, exc_value[, traceback]])
使生成器在暫停的 yield 表達(dá)式處拋出指定的異常。如果生成器處理了拋出的異常,代碼會(huì)向前執(zhí)行到下一個(gè) yield 表達(dá)式,而產(chǎn)出的值會(huì)成為調(diào)用 generator.throw方法得到的返回值。如果生成器沒(méi)有處理拋出的異常,異常會(huì)向上冒泡,傳到調(diào)用方的上下文中。
2:generator.close()
使生成器在暫停的 yield 表達(dá)式處拋出 GeneratorExit 異常。如果生成器沒(méi)有處理這個(gè)異常,或者拋出了 StopIteration 異常(通常是指運(yùn)行到結(jié)尾),調(diào)用方不會(huì)報(bào)錯(cuò)。如果收到 GeneratorExit 異常,生成器一定不能產(chǎn)出值,否則解釋器會(huì)拋出RuntimeError 異常。生成器拋出的其他異常會(huì)向上冒泡,傳給調(diào)用方。
示例如下:
class DemoException(Exception):
"""為這次演示定義的異常類(lèi)型。"""
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'六:讓協(xié)程返回值
在Python2中,生成器函數(shù)中的return不允許返回附帶返回值。在Python3中取消了這一限制,因而允許協(xié)程可以返回值:
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) 發(fā)送 None 會(huì)終止循環(huán),導(dǎo)致協(xié)程結(jié)束,返回結(jié)果。一如既往,生成器對(duì)象會(huì)拋出StopIteration 異常。異常對(duì)象的 value 屬性保存著返回的值。
注意,return 表達(dá)式的值會(huì)偷偷傳給調(diào)用方,賦值給 StopIteration 異常的一個(gè)屬性。這樣做有點(diǎn)不合常理,但是能保留生成器對(duì)象的常規(guī)行為——耗盡時(shí)拋出StopIteration 異常。如果需要接收返回值,可以這樣:
>>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result Result(count=3, average=15.5)
獲取協(xié)程的返回值要繞個(gè)圈子,可以使用Python3.3引入的yield from獲取返回值。yield from 結(jié)構(gòu)會(huì)在內(nèi)部自動(dòng)捕獲 StopIteration 異常。這種處理方式與 for 循環(huán)處理 StopIteration 異常的方式一樣。對(duì) yield from 結(jié)構(gòu)來(lái)說(shuō),解釋器不僅會(huì)捕獲 StopIteration 異常,還會(huì)把value 屬性的值變成 yield from 表達(dá)式的值。
七:使用yield from
yield from 是 Python3.3 后新加的語(yǔ)言結(jié)構(gòu)。在其他語(yǔ)言中,類(lèi)似的結(jié)構(gòu)使用 await 關(guān)鍵字,這個(gè)名稱(chēng)好多了,因?yàn)樗鼈鬟_(dá)了至關(guān)重要的一點(diǎn):在生成器 gen 中使用 yield from subgen() 時(shí),subgen 會(huì)獲得控制權(quán),把產(chǎn)出的值傳給 gen 的調(diào)用方,即調(diào)用方可以直接控制 subgen。與此同時(shí),gen 會(huì)阻塞,等待 subgen 終止。
yield from 可用于簡(jiǎn)化 for 循環(huán)中的 yield 表達(dá)式。例如:
>>> 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 表達(dá)式對(duì) x 對(duì)象所做的第一件事是,調(diào)用 iter(x),從中獲取迭代器。因此,x 可以是任何可迭代的對(duì)象。
如果 yield from 結(jié)構(gòu)唯一的作用是替代產(chǎn)出值的嵌套 for 循環(huán),這個(gè)結(jié)構(gòu)很有可能不會(huì)添加到 Python 語(yǔ)言中。
yield from 的主要功能是打開(kāi)雙向通道,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來(lái),這樣二者可以直接發(fā)送和產(chǎn)出值,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量處理異常的樣板代碼。有了這個(gè)結(jié)構(gòu),協(xié)程可以通過(guò)以前不可能的方式委托職責(zé)。
PEP 380 使用了一些yield from使用的專(zhuān)門(mén)術(shù)語(yǔ):
委派生成器:包含 yield from <iterable> 表達(dá)式的生成器函數(shù);
子生成器:從 yield from 表達(dá)式中 <iterable> 部分獲取的生成器;
調(diào)用方:調(diào)用委派生成器的客戶(hù)端代碼;
下圖是這三者之間的交互關(guān)系:

委派生成器在 yield from 表達(dá)式處暫停時(shí),調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器,子生成器再把產(chǎn)出的值發(fā)給調(diào)用方。子生成器返回之后,解釋器會(huì)拋出StopIteration 異常,并把返回值附加到異常對(duì)象上,此時(shí)委派生成器會(huì)恢復(fù)。
下面是一個(gè)求平均身高和體重的示例代碼:
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# 子生成器
def averager():
total = 0.0
count = 0
average = None
while True:
# main 函數(shù)發(fā)送數(shù)據(jù)到這里
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 會(huì)成為grouper函數(shù)中yield from表達(dá)式的值
# 委派生成器
def grouper(results, key):
# 這個(gè)循環(huán)每次都會(huì)新建一個(gè)averager 實(shí)例,每個(gè)實(shí)例都是作為協(xié)程使用的生成器對(duì)象
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)
# 調(diào)用方
def main(data):
results = {}
for key, values in data.items():
# group 是調(diào)用grouper函數(shù)得到的生成器對(duì)象
group = grouper(results, key)
print("\ncreate group: ", group)
next(group) #預(yù)激 group 協(xié)程。
print("pre active group ok")
for value in values:
# 把各個(gè)value傳給grouper 傳入的值最終到達(dá)averager函數(shù)中;
# grouper并不知道傳入的是什么,同時(shí)grouper實(shí)例在yield from處暫停
print("send to %r value %f now"%(group, value))
group.send(value)
# 把None傳入groupper,傳入的值最終到達(dá)averager函數(shù)中,導(dǎo)致當(dāng)前實(shí)例終止。然后繼續(xù)創(chuàng)建下一個(gè)實(shí)例。
# 如果沒(méi)有g(shù)roup.send(None),那么averager子生成器永遠(yuǎn)不會(huì)終止,委派生成器也永遠(yuǎn)不會(huì)在此激活,也就不會(huì)為result[key]賦值
print("send to %r none"%group)
group.send(None)
print("report result: ")
report(results)
# 輸出報(bào)告
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 發(fā)送的每個(gè)值都會(huì)經(jīng)由 yield from 處理,通過(guò)管道傳給 averager 實(shí)例。grouper 會(huì)在 yield from 表達(dá)式處暫停,等待 averager 實(shí)例處理客戶(hù)端發(fā)來(lái)的值。averager 實(shí)例運(yùn)行完畢后,返回的值綁定到 results[key] 上。while 循環(huán)會(huì)不斷創(chuàng)建 averager 實(shí)例,處理更多的值。
外層 for 循環(huán)重新迭代時(shí)會(huì)新建一個(gè) grouper 實(shí)例,然后綁定到 group 變量上。前一個(gè) grouper 實(shí)例(以及它創(chuàng)建的尚未終止的 averager 子生成器實(shí)例)被垃圾回收程序回收。
代碼結(jié)果如下:
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
這個(gè)試驗(yàn)想表明的關(guān)鍵一點(diǎn)是,如果子生成器不終止,委派生成器會(huì)在yield from 表達(dá)式處永遠(yuǎn)暫停。如果是這樣,程序不會(huì)向前執(zhí)行,因?yàn)?yield from(與 yield 一樣)把控制權(quán)轉(zhuǎn)交給客戶(hù)代碼(即,委派生成器的調(diào)用方)了。
八:yield from的意義
把迭代器當(dāng)作生成器使用,相當(dāng)于把子生成器的定義體內(nèi)聯(lián)在 yield from 表達(dá)式中。此外,子生成器可以執(zhí)行 return 語(yǔ)句,返回一個(gè)值,而返回的值會(huì)成為 yield from 表達(dá)式的值。
PEP 380 在“Proposal”一節(jié)(https://www.python.org/dev/peps/pep-0380/#proposal)分六點(diǎn)說(shuō)明了 yield from 的行為。這里幾乎原封不動(dòng)地引述,不過(guò)把有歧義的“迭代器”一詞都換成了“子生成器”,還做了進(jìn)一步說(shuō)明。上面的示例闡明了下述四點(diǎn):
子生成器產(chǎn)出的值都直接傳給委派生成器的調(diào)用方(即客戶(hù)端代碼);
使用 send() 方法發(fā)給委派生成器的值都直接傳給子生成器。如果發(fā)送的值是None,那么會(huì)調(diào)用子生成器的 __next__() 方法。如果發(fā)送的值不是 None,那么會(huì)調(diào)用子生成器的 send() 方法。如果子生成器拋出 StopIteration 異常,那么委派生成器恢復(fù)運(yùn)行。任何其他異常都會(huì)向上冒泡,傳給委派生成器;
生成器退出時(shí),生成器(或子生成器)中的 return expr 表達(dá)式會(huì)觸發(fā)StopIteration(expr) 異常拋出;
yield from 表達(dá)式的值是子生成器終止時(shí)傳給 StopIteration 異常的第一個(gè)參數(shù)。
yield from 的具體語(yǔ)義很難理解,尤其是處理異常的那兩點(diǎn)。在PEP 380 中闡述了 yield from 的語(yǔ)義。還使用偽代碼(使用 Python 句法)演示了 yield from 的行為。
若想研究那段偽代碼,最好將其簡(jiǎn)化,只涵蓋 yield from 最基本且最常見(jiàn)的用法:yield from 出現(xiàn)在委派生成器中,客戶(hù)端代碼驅(qū)動(dòng)著委派生成器,而委派生成器驅(qū)動(dòng)著子生成器。為了簡(jiǎn)化涉及到的邏輯,假設(shè)客戶(hù)端沒(méi)有在委派生成器上調(diào)用throw(...) 或 close() 方法。而且假設(shè)子生成器不會(huì)拋出異常,而是一直運(yùn)行到終止,讓解釋器拋出 StopIteration 異常。上面示例中的腳本就做了這些簡(jiǎn)化邏輯的假設(shè)。
下面的偽代碼,等效于委派生成器中的 RESULT = yield from EXPR 語(yǔ)句(這里針對(duì)的是最簡(jiǎn)單的情況:不支持 .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但是,現(xiàn)實(shí)情況要復(fù)雜一些,因?yàn)橐幚砜蛻?hù)對(duì) throw(...) 和 close() 方法的調(diào)用,而這兩個(gè)方法執(zhí)行的操作必須傳入子生成器。此外,子生成器可能只是純粹的迭代器,不支持 throw(...) 和 close() 方法,因此 yield from 結(jié)構(gòu)的邏輯必須處理這種情況。如果子生成器實(shí)現(xiàn)了這兩個(gè)方法,而在子生成器內(nèi)部,這兩個(gè)方法都會(huì)觸發(fā)異常拋出,這種情況也必須由 yield from 機(jī)制處理。調(diào)用方可能會(huì)無(wú)緣無(wú)故地讓子生成器自己拋出異常,實(shí)現(xiàn) yield from 結(jié)構(gòu)時(shí)也必須處理這種情況。最后,為了優(yōu)化,如果調(diào)用方調(diào)用 next(...) 函數(shù)或 .send(None) 方法,都要轉(zhuǎn)交職責(zé),在子生成器上調(diào)用next(...) 函數(shù);僅當(dāng)調(diào)用方發(fā)送的值不是 None 時(shí),才使用子生成器的 .send(...) 方法。
下面的偽代碼,是考慮了上述情況之后,語(yǔ)句: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上面的偽代碼中,會(huì)預(yù)激子生成器。這表明,用于自動(dòng)預(yù)激的裝飾器與 yield from 結(jié)構(gòu)不兼容。
總結(jié)
到此這篇關(guān)于python協(xié)程之yield和yield from的文章就介紹到這了,更多相關(guān)python協(xié)程yield和yield from內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
pycharm中django框架連接mysql數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了pycharm中django框架連接mysql數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Django使用原生SQL查詢(xún)數(shù)據(jù)庫(kù)詳解
本文介紹了Django ORM的優(yōu)缺點(diǎn),然后介紹了使用原生SQL進(jìn)行查詢(xún)的優(yōu)點(diǎn),包括更靈活、更高效等。接著介紹了如何在Django中使用原生SQL進(jìn)行查詢(xún),包括利用Django的connection對(duì)象進(jìn)行查詢(xún)以及使用Django的CursorWrapper類(lèi)進(jìn)行封裝。最后提醒了使用原生SQL查詢(xún)的注意事項(xiàng)。2023-04-04
Python?Pandas中l(wèi)oc和iloc函數(shù)的基本用法示例
無(wú)論是loc還是iloc都是pandas中數(shù)據(jù)篩選的函數(shù),下面這篇文章主要給大家介紹了關(guān)于Python?Pandas中l(wèi)oc和iloc函數(shù)的基本用法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
對(duì)python中的pop函數(shù)和append函數(shù)詳解
今天小編就為大家分享一篇對(duì)python中的pop函數(shù)和append函數(shù)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Python存取XML的常見(jiàn)方法實(shí)例分析
這篇文章主要介紹了Python存取XML的常見(jiàn)方法,結(jié)合具體實(shí)例形式較為詳細(xì)的分析了Python存取xml的常用方法、優(yōu)缺點(diǎn)比較與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-03-03
Python 使用pandas實(shí)現(xiàn)查詢(xún)和統(tǒng)計(jì)示例詳解
這篇文章主要為大家介紹了Python 使用pandas實(shí)現(xiàn)查詢(xún)和統(tǒng)計(jì)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Python爬蟲(chóng)獲取全網(wǎng)招聘數(shù)據(jù)實(shí)現(xiàn)可視化分析示例詳解
這篇文章主要介紹了Python爬蟲(chóng)獲取全網(wǎng)招聘數(shù)據(jù)實(shí)現(xiàn)可視化分析示例詳解,實(shí)現(xiàn)采集一下最新的qcwu招聘數(shù)據(jù),本文列舉了部分代碼以及實(shí)現(xiàn)思路,需要的朋友可以參考下2023-07-07
利用Python自動(dòng)化識(shí)別與刪除Excel表格空白行和列
在處理Excel數(shù)據(jù)時(shí),經(jīng)常會(huì)遇到含有空白行和空白列的情況,本文將介紹如何利用Python來(lái)自動(dòng)化識(shí)別并刪除Excel文件中的空白行和列,從而確保數(shù)據(jù)集的整潔性,為后續(xù)的數(shù)據(jù)分析工作奠定堅(jiān)實(shí)的基礎(chǔ),需要的朋友可以參考下2024-05-05

