[譯]PEP 342--增強(qiáng)型生成器:協(xié)程

adorable-animal-animal-photography-978555.jpg

PEP原文 : https://www.python.org/dev/peps/pep-0342/

PEP標(biāo)題: Coroutines via Enhanced Generators

PEP作者: Guido van Rossum, Phillip J. Eby

創(chuàng)建日期: 2005-05-10

合入版本: 2.5

譯者豌豆花下貓Python貓 公眾號(hào)作者)

目錄

  • 簡(jiǎn)介
  • 動(dòng)機(jī)
  • 規(guī)格摘要
  • 規(guī)格:將值發(fā)送到生成器
    • 新的生成器方法:send(value)
    • 新的語(yǔ)法:yield 表達(dá)式
  • 規(guī)格:異常和清理
    • 新語(yǔ)法:yield 允許在try-finally
    • 新的生成器方法:throw(type革半,value = None育叁,traceback = None)
    • 新的標(biāo)準(zhǔn)異常:GeneratorExit
    • 新的生成器方法:close()
    • 新的生成器方法:__del__()
  • 可選的擴(kuò)展
    • 擴(kuò)展的 continue 表達(dá)式
  • 未決問(wèn)題
  • 示例
  • 參考實(shí)現(xiàn)
  • 致謝
  • 參考文獻(xiàn)
  • 版權(quán)

簡(jiǎn)介

這個(gè) PEP 在生成器的 API 和語(yǔ)法方面涂身,提出了一些增強(qiáng)功能逼争,使得它們可以作為簡(jiǎn)單的協(xié)程使用熄驼。這基本上是將下述兩個(gè) PEP 的想法結(jié)合起來(lái)像寒,如果它被采納烘豹,那它們就是多余的了:

  • PEP-288,關(guān)于生成器的屬性特征與異常(Attributes and Exceptions)诺祸。當(dāng)前 PEP 沿用了它的下半部分携悯,即生成器的異常(事實(shí)上,throw() 的方法名就取自 PEP-288)筷笨。PEP-342 用 yield 表達(dá)式(這個(gè)概念來(lái)自 PEP-288 的早期版本)來(lái)替換了生成器的屬性特征憔鬼。
  • PEP-325,生成器支持釋放資源胃夏。PEP-342 收緊了 PEP-325 中的一些松散的規(guī)范轴或,使其更適用于實(shí)際的實(shí)現(xiàn)。

(譯注:PEP-288 和 PEP-325 都沒(méi)有被采納通過(guò)仰禀,它們的核心內(nèi)容被集成到了 PEP-342里照雁。)

動(dòng)機(jī)

協(xié)程是表達(dá)許多算法的自然方式,例如模擬/仿真答恶、游戲饺蚊、異步 I/O、以及其它事件驅(qū)動(dòng)編程或協(xié)同的多任務(wù)處理悬嗓。Python 的生成器函數(shù)幾乎就是協(xié)程——但不完全是——因?yàn)樗鼈冊(cè)试S暫停來(lái)生成值污呼,但又不允許在程序恢復(fù)時(shí)傳入值或異常。它們也不允許在 try-finally 結(jié)構(gòu)的 try 部分作暫停包竹,因此很難令一個(gè)異常退出的(aborted)協(xié)程來(lái)清理自己燕酷。

同樣地,當(dāng)其它函數(shù)在執(zhí)行時(shí)映企,生成器不能提供控制悟狱,除非這些函數(shù)本身是生成器,并且外部生成器之所以寫(xiě)了去 yield堰氓,是要為了響應(yīng)內(nèi)部生成器所 yield 的值挤渐。這使得即使是相對(duì)簡(jiǎn)單的實(shí)現(xiàn)(如異步通信)也變得復(fù)雜,因?yàn)檎{(diào)用任意函數(shù)双絮,要么需要生成器變堵塞(block浴麻,即無(wú)法提供控制),要么必須在每個(gè)要調(diào)用的函數(shù)的周?chē)谂剩砑右淮蠖岩醚h(huán)代碼(a lot of boilerplate looping code)软免。

但是,如果有可能在生成器掛起的點(diǎn)上傳遞進(jìn)來(lái)值或者異常焚挠,那么膏萧,一個(gè)簡(jiǎn)單的協(xié)程調(diào)度器或蹦床函數(shù)(trampoline function)就能使協(xié)程相互調(diào)用且不用阻塞——對(duì)異步應(yīng)用程序有巨大好處。這些應(yīng)用程序可以編寫(xiě)協(xié)程來(lái)運(yùn)行非阻塞的 socket I/O,通過(guò)給 I/O 調(diào)度器提供控制榛泛,直到數(shù)據(jù)被發(fā)送或變?yōu)榭捎抿蝓濉M瑫r(shí),執(zhí)行 I/O 的代碼只需像如下方式操作曹锨,就能暫停執(zhí)行孤个,直到 nonblocking_read() 繼續(xù)產(chǎn)生一個(gè)值:

data = (yield nonblocking_read(my_socket, nbytes))

換句話說(shuō), 通過(guò)給語(yǔ)言和生成器類(lèi)型增加一些相對(duì)較小的增強(qiáng)沛简,Python 不需要為整個(gè)程序編寫(xiě)一系列回調(diào)齐鲤,就能支持異步操作,并且對(duì)于本該需要數(shù)百上千個(gè)協(xié)作式的多任務(wù)偽線程的(co-operatively multitasking pseudothreads)程序椒楣,也可以不需要使用資源密集型線程给郊。因此,這些增強(qiáng)功能將給標(biāo)準(zhǔn) Python 帶來(lái) Stackless Python 的許多優(yōu)點(diǎn)捧灰,又無(wú)需對(duì) CPython 核心及其 API 進(jìn)行任何重大的修改丑罪。此外,這些增強(qiáng)在任何已經(jīng)支持生成器的 Python 實(shí)現(xiàn)(例如 Jython)上都是可落實(shí)的凤壁。

規(guī)格摘要

通過(guò)給生成器類(lèi)型增加一些簡(jiǎn)單的方法吩屹,以及兩個(gè)微小的語(yǔ)法調(diào)整,Python 開(kāi)發(fā)者就能夠使用生成器函數(shù)來(lái)實(shí)現(xiàn)協(xié)程與其它的協(xié)作式多任務(wù)拧抖。這些方法和調(diào)整是:

  1. 重定義 yield 為表達(dá)式(expression)煤搜,而不是語(yǔ)句(statement)。當(dāng)前的 yield 語(yǔ)句將變成一個(gè) yield 表達(dá)式唧席,其值將被丟棄擦盾。每當(dāng)通過(guò)正常的 next() 調(diào)用來(lái)恢復(fù)生成器時(shí),yield 表達(dá)式的返回值是 None淌哟。
  2. 為生成器(generator-iterator)添加一個(gè)新的 send() 方法迹卢,它會(huì)恢復(fù)生成器,并且 send 一個(gè)值作為當(dāng)前表達(dá)式的結(jié)果徒仓。send() 方法返回的是生成器產(chǎn)生的 next 值腐碱,若生成器沒(méi)有產(chǎn)生值就退出的話,則拋出 StopIteration 掉弛。
  3. 為生成器(generator-iterator)添加一個(gè)新的 throw() 方法症见,它在生成器暫停處拋出異常,并返回生成器產(chǎn)生的下一個(gè)值殃饿,若生成器沒(méi)有產(chǎn)生值就退出的話谋作,則拋出 StopIteration (如果生成器沒(méi)有捕獲傳入的異常,或者它引發(fā)了其它異常乎芳,則該異常會(huì)傳遞給調(diào)用者遵蚜。)
  4. 為生成器(generator-iterator)添加一個(gè)新的 close() 方法帖池,它在生成器暫停處引發(fā) GeneratorExit 。如果生成器在之后引發(fā) StopIteration (通過(guò)正常退出吭净,或者已經(jīng)被關(guān)閉)或 GeneratorExit (通過(guò)不捕獲異常)碘裕,則 close() 返回給其調(diào)用者。如果生成器產(chǎn)生一個(gè)值攒钳,則拋出 RuntimeError。如果生成器引發(fā)任何其它異常雷滋,也會(huì)傳遞給調(diào)用者不撑。如果生成器已經(jīng)退出(異常退出或正常退出),則 close() 不執(zhí)行任何操作晤斩。
  5. 增加了支持焕檬,確保即使在生成器被垃圾回收時(shí),也會(huì)調(diào)用 close()澳泵。
  6. 允許 yield 在 try-finally 塊中使用实愚,因?yàn)楝F(xiàn)在允許在 finally 語(yǔ)句中執(zhí)行垃圾回收或顯式地調(diào)用 close() 。

實(shí)現(xiàn)了所有這些變更的原型補(bǔ)丁已經(jīng)可用了兔辅,可作為當(dāng)前 Python CVS HEAD 的 SourceForge 補(bǔ)丁腊敲。# 1223381

設(shè)計(jì)規(guī)格:將值發(fā)送進(jìn)生成器

新的生成器方法:send(value)

為生成器提出了一種新的方法,即 send() 维苔。它只接收一個(gè)參數(shù)碰辅,并將它發(fā)送給生成器。調(diào)用 send(None) 完全等同于調(diào)用生成器的 next() 方法介时。使用其它參數(shù)調(diào)用 send() 也有同樣的效果没宾,不同的是,當(dāng)前生成器表達(dá)式產(chǎn)生的值會(huì)不一樣沸柔。

因?yàn)樯善髟谏善骱瘮?shù)體的頭部執(zhí)行循衰,所以在剛剛創(chuàng)建生成器時(shí)不會(huì)有 yield 表達(dá)式來(lái)接收值,因此褐澎,當(dāng)生成器剛啟動(dòng)時(shí)会钝,禁止使用非 None 參數(shù)來(lái)調(diào)用 send() ,如果調(diào)用了工三,就會(huì)拋出 TypeError (可能是由于某種邏輯錯(cuò)誤)顽素。所以,在與協(xié)程通信前徒蟆,必須先調(diào)用 next() 或 send(None) 胁出,來(lái)將程序推進(jìn)到第一個(gè) yield 表達(dá)式。

與 next() 方法一樣段审,send() 方法也返回生成器產(chǎn)生的下一個(gè)值全蝶,或者拋出 StopIteration 異常(當(dāng)生成器正常退出,或早已退出時(shí))。如果生成器出現(xiàn)未捕獲的異常抑淫,則它會(huì)傳給調(diào)用者绷落。

新語(yǔ)法:yield 表達(dá)式

yield 語(yǔ)句(yield-statement)可以被用在賦值表達(dá)式的右側(cè);在這種情況下始苇,它就是 yield 表達(dá)式(yield-expression)砌烁。除非使用非 None 參數(shù)調(diào)用 send() ,否則 yield 表達(dá)式的值就是 None催式。見(jiàn)下文函喉。

yield 表達(dá)式必須始終用括號(hào)括起來(lái),除非它是作為頂級(jí)表達(dá)式而出現(xiàn)在賦值表達(dá)式的右側(cè)荣月。所以管呵,下面例子都是合法的:

x = yield 42
x = yield
x = 12 + (yield 42)
x = 12 + (yield)
foo(yield 42)
foo(yield)

而下面的例子則是非法的(舉了一些特例的原因是,當(dāng)前的 yield 12,42 是合法的):

x = 12 + yield 42
x = 12 + yield
foo(yield 42, 12)
foo(yield, 12)

請(qǐng)注意哺窄,如今沒(méi)有表達(dá)式的 yield-語(yǔ)句 和 yield-表達(dá)式是合法的捐下。這意味著:當(dāng) next() 調(diào)用中的信息流被反轉(zhuǎn)時(shí),應(yīng)該可以在不傳遞顯式的值的情況下 yield (yield 當(dāng)然就等同于 yield None)萌业。

當(dāng)調(diào)用 send(value) 時(shí)坷襟,它恢復(fù)的 yield 表達(dá)式將返回傳入的值。當(dāng)調(diào)用 next() 時(shí)生年,它恢復(fù)的 yield 表達(dá)式將返回 None啤握。如果 yield-表達(dá)式(yield-expression)是一個(gè) yield-語(yǔ)句(yield-statement),其返回值會(huì)被忽略晶框,就類(lèi)似于忽略用作語(yǔ)句的函數(shù)的返回值排抬。

實(shí)際上,yield 表達(dá)式就像一個(gè)反函數(shù)調(diào)用(inverted function)授段;它所 yield 的值實(shí)際上是當(dāng)前函數(shù)返回(生成)的蹲蒲,而它 return 的值則是通過(guò) send() 傳入的參數(shù)。

提示:這樣的拓展語(yǔ)法侵贵,使得它非常地接近于 Ruby届搁。這是故意的。請(qǐng)注意窍育,Python 在阻塞時(shí)卡睦,通過(guò)使用 send(EXPR) 而不是 return EXPR 來(lái)傳值給生成器,并且在生成器與阻塞之間傳遞控制權(quán)的底層機(jī)制完全不同漱抓。Python 中的阻塞不會(huì)被編譯成 thunk表锻,相反,yield 暫停生成器的執(zhí)行進(jìn)度乞娄。有一些不是這樣的特例瞬逊,在 Python 中显歧,你不能保存阻塞以供后續(xù)調(diào)用,并且你無(wú)法測(cè)試是否存在著阻塞确镊。(XXX - 關(guān)于阻塞的這些東西似乎不合適士骤,或許 Guido 會(huì)編輯下,做澄清蕾域。)

設(shè)計(jì)規(guī)格:異常和清理

讓生成器對(duì)象成為通過(guò)調(diào)用生成器函數(shù)而生成的迭代器拷肌。本節(jié)中的 g 指的都是生成器對(duì)象。

新語(yǔ)法:yield 允許在 try-finally 里

生成器函數(shù)的語(yǔ)法被拓展了旨巷,允許在 try-finally 語(yǔ)句中使用 yield 語(yǔ)句巨缘。

新的生成器方法:throw(type,value = None契沫,traceback = None)

g.throw(type, value, traceback) 會(huì)使生成器在掛起的點(diǎn)處拋出指定的異常(即在 yield 語(yǔ)句中,或在其函數(shù)體的頭部昔汉、且還未調(diào)用 next() 時(shí))懈万。如果生成器捕獲了異常,并生成了新的值靶病,則它就是 g.throw() 的返回值会通。如果生成器沒(méi)有捕獲異常,那 throw() 也會(huì)拋出同樣的異常(它溜走了)娄周。如果生成器拋出其它異常(包括返回時(shí)產(chǎn)生的 StopIteration)涕侈,那該異常會(huì)被 throw() 拋出∶罕妫總之裳涛,throw() 的行為類(lèi)似于 next() 或 send(),除了它是在掛起點(diǎn)處拋出異常众辨。如果生成器已經(jīng)處于關(guān)閉狀態(tài)端三,throw() 只會(huì)拋出經(jīng)過(guò)它的異常,而不去執(zhí)行生成器的任何代碼鹃彻。

拋出異常的效果完全像它所聲明的那樣:

raise type, value, traceback

會(huì)在暫停點(diǎn)執(zhí)行郊闯。type 參數(shù)不能是 None,且 type 與 value 的類(lèi)型必須得兼容蛛株。如果 value 不是 type 的實(shí)例(instance)团赁,則按照 raise 語(yǔ)句創(chuàng)建異常實(shí)例的規(guī)則,用 value 來(lái)生成新的異常實(shí)例谨履。如果提供了 traceback 參數(shù)欢摄,則它必須是有效的 Python 堆棧(traceback)對(duì)象,否則會(huì)拋出 TypeError 笋粟。

注釋?zhuān)哼x擇 throw() 這個(gè)名稱(chēng)剧浸,有幾個(gè)原因锹引。Raise 是一個(gè)關(guān)鍵字,因此不能作為方法的名稱(chēng)唆香。與 raise 不同(它在執(zhí)行點(diǎn)處即時(shí)地拋出異常)嫌变,throw() 首先恢復(fù)生成器,然后才拋出異常躬它。單詞 throw 意味著將異常拋在別處腾啥,并且跟其它語(yǔ)言相關(guān)聯(lián)。

考慮了幾個(gè)替代的方法名:resolve(), signal(), genraise(), raiseinto()flush() 冯吓。沒(méi)有一個(gè)像 throw() 那般合適倘待。

新的標(biāo)準(zhǔn)異常:GeneratorExit

定義了一個(gè)新的標(biāo)準(zhǔn)異常 GeneratorExit,繼承自 Exception组贺。生成器應(yīng)該繼續(xù)拋出它(或者就不捕獲它)凸舵,或者通過(guò)拋出 StopIteration 來(lái)處理這個(gè)問(wèn)題。

新的生成器方法:close()

g.close() 由以下偽代碼定義:

def close(self):
    try:
        self.throw(GeneratorExit)
    except (GeneratorExit, StopIteration):
        pass
    else:
        raise RuntimeError("generator ignored GeneratorExit")
    # Other exceptions are not caught

新的生成器方法:__del__()

g.__ del __() 是 g.close() 的裝飾器失尖。當(dāng)生成器對(duì)象被作垃圾回收時(shí)啊奄,會(huì)調(diào)用它(在 CPython 中,則是它的引用計(jì)數(shù)變?yōu)榱銜r(shí))掀潮。如果 close() 引發(fā)異常菇夸, 異常的堆棧信息(traceback)會(huì)被打印到 sys.stderr 并被忽略掉;它不會(huì)退回到觸發(fā)垃圾回收的地方仪吧。這與類(lèi)實(shí)例在處理 __del__()的異常時(shí)的方法一樣庄新。

如果生成器對(duì)象被循環(huán)引用,則可能不會(huì)調(diào)用 g.__del__() 薯鼠。這是當(dāng)前 CPython 的垃圾收集器的表現(xiàn)择诈。做此限制的原因是,GC 代碼需要在一個(gè)任意點(diǎn)打破循環(huán)出皇,以便回收它吭从,在此之后,不允許 Python 代碼“看到”形成循環(huán)的對(duì)象恶迈,因?yàn)樗鼈兛赡芴幱跓o(wú)效的狀態(tài)涩金。被用于解開(kāi)(hanging off)循環(huán)的對(duì)象不受此限制。

盡管實(shí)際上不太可能看到生成器被循環(huán)引用暇仲。但是步做,若將生成器對(duì)象存儲(chǔ)在全局變量中,則會(huì)通過(guò)生成器框架的 f_globals 指針創(chuàng)建一個(gè)循環(huán)奈附。另外全度,若在數(shù)據(jù)結(jié)構(gòu)中存儲(chǔ)對(duì)生成器對(duì)象的引用,且該數(shù)據(jù)結(jié)構(gòu)被作為參數(shù)傳遞給生成器斥滤,這也會(huì)創(chuàng)造一個(gè)循環(huán)引用(例如将鸵,如果一個(gè)對(duì)象具有一個(gè)作為生成器的方法勉盅,并持有由該方法創(chuàng)建的運(yùn)行中的迭代器的引用)。鑒于生成器的典型用法顶掉,這些情況都不太可能草娜。

此外,CPython 在實(shí)現(xiàn)當(dāng)前 PEP 時(shí)痒筒,每當(dāng)由于錯(cuò)誤或正常退出而終止執(zhí)行時(shí)宰闰,會(huì)釋放被生成器使用的框架對(duì)象(frame object)。這保證了那些無(wú)法被恢復(fù)的生成器不會(huì)成為無(wú)法回收的循環(huán)引用的部分簿透。這就允許了其它代碼在 try-finally 或 with 語(yǔ)句中使用 close() (參考 PEP-343)移袍,確保了給定的生成器會(huì)正確地完結(jié)。

可選擴(kuò)展

擴(kuò)展的 continue 語(yǔ)句

本 PEP 的早期草案提出了一種新的 continue EXPR 語(yǔ)法老充,用于 for 循環(huán)(繼承自 PEP-340)葡盗,將 EXPR 的值傳給被遍歷的迭代器。此功能暫時(shí)被撤銷(xiāo)了啡浊,因?yàn)楸?PEP 的范圍已經(jīng)縮小觅够,只關(guān)注將值傳給生成器迭代器(generator-iterator),而非其它類(lèi)型的迭代器虫啥。Python-Dev 郵件列表中的一些人也覺(jué)得為這個(gè)特定功能添加新語(yǔ)法是為時(shí)過(guò)早(would be premature at best)蔚约。

未決問(wèn)題

Python-Dev 郵件的討論提出了一些未決的問(wèn)題奄妨。我羅列于此涂籽,附上我推薦的解決方案與它的動(dòng)機(jī)。目前編寫(xiě)的 PEP 也反映了這種喜好的解決方案砸抛。

  1. 當(dāng)生成器產(chǎn)生另一個(gè)值作為對(duì)“GeneratorExit”異常的響應(yīng)時(shí)评雌,close()應(yīng)該引發(fā)什么異常?

    我最初選擇了 TypeError 直焙,因?yàn)樗硎旧善骱瘮?shù)發(fā)生了嚴(yán)重的錯(cuò)誤行為景东,應(yīng)該通過(guò)修改代碼來(lái)修復(fù)。但是 PEP-343 中的 with_template 裝飾器類(lèi)使用了 RuntimeError 來(lái)進(jìn)行類(lèi)似處理奔誓〗锿拢可以說(shuō)它們都應(yīng)該使用相同的異常。我寧愿不為此目的引入新的異常類(lèi)厨喂,因?yàn)樗皇俏蚁M藗儾东@的異常:我希望它變成一個(gè) traceback 給程序員看到和措,然后進(jìn)行修復(fù)。所以我覺(jué)得它們都應(yīng)該拋出 RuntimeError 蜕煌。有一些先例:在檢測(cè)到無(wú)限遞歸的情況下派阱,或者檢測(cè)到未初始化的對(duì)象(由于各種各樣的原因),核心 Python 代碼會(huì)拋出該異常斜纪。

  2. Oren Tirosh 建議將 send() 方法重命名為 feed() 文兑,以便能跟 consumer 接口兼容(規(guī)范參見(jiàn):http://effbot.org/zone/consumer.htm)腺劣。

然而,仔細(xì)觀察 consumer 接口誓酒,似乎 feed() 所需的語(yǔ)義與 send() 不同,因?yàn)楹笳卟荒茉趧倖?dòng)的生成器上作有意義的調(diào)用靠柑。此外,當(dāng)前定義的 consumer 接口不包含對(duì) StopIteration 的處理歼冰。

因此,創(chuàng)建一個(gè)貼合 consumer 接口的簡(jiǎn)單的裝飾器隔嫡,來(lái)裝飾生成器函數(shù),似乎會(huì)更有用梢杭。舉個(gè)例子,它可以用初始的 next() 調(diào)用給生成器預(yù)熱(warm up)秸滴,追蹤 StopIteration武契,甚至可以通過(guò)重新調(diào)用生成器來(lái)提供 reset() 用途。

示例

  1. 一個(gè)簡(jiǎn)單的 consumer 裝飾器荡含,它使生成器函數(shù)在最初調(diào)用時(shí)咒唆,就自動(dòng)地前進(jìn)到第一個(gè) yield 點(diǎn):
def consumer(func):
    def wrapper(*args,**kw):
        gen = func(*args, **kw)
        gen.next()
        return gen
    wrapper.__name__ = func.__name__
    wrapper.__dict__ = func.__dict__
    wrapper.__doc__  = func.__doc__
    return wrapper
  1. 一個(gè)使用 consumer 裝飾器創(chuàng)建反向生成器(reverse generator)的示例,該生成器接收?qǐng)D像并創(chuàng)建縮略圖释液,再發(fā)送給其它 consumer全释。像這樣的函數(shù)可以鏈接在一起,形成 consumer 間的高效處理流水線误债,且每個(gè)流水線都可以具有復(fù)雜的內(nèi)部狀態(tài):
@consumer
def thumbnail_pager(pagesize, thumbsize, destination):
    while True:
        page = new_image(pagesize)
        rows, columns = pagesize / thumbsize
        pending = False
        try:
            for row in xrange(rows):
                for column in xrange(columns):
                    thumb = create_thumbnail((yield), thumbsize)
                    page.write(
                        thumb, col*thumbsize.x, row*thumbsize.y )
                    pending = True
        except GeneratorExit:
            # close() was called, so flush any pending output
            if pending:
                destination.send(page)

            # then close the downstream consumer, and exit
            destination.close()
            return
        else:
            # we finished a page full of thumbnails, so send it
            # downstream and keep on looping
            destination.send(page)

@consumer
def jpeg_writer(dirname):
    fileno = 1
    while True:
        filename = os.path.join(dirname,"page%04d.jpg" % fileno)
        write_jpeg((yield), filename)
        fileno += 1


# Put them together to make a function that makes thumbnail
# pages from a list of images and other parameters.
#
def write_thumbnails(pagesize, thumbsize, images, output_dir):
    pipeline = thumbnail_pager(
        pagesize, thumbsize, jpeg_writer(output_dir)
    )

    for image in images:
        pipeline.send(image)

    pipeline.close()
  1. 一個(gè)簡(jiǎn)單的協(xié)程調(diào)度器或蹦床(trampoline)浸船,它允許協(xié)程通過(guò) yield 其它協(xié)程,來(lái)調(diào)用后者寝蹈。被調(diào)用的協(xié)程所產(chǎn)生的非生成器的值李命,會(huì)被返回給調(diào)用方的協(xié)程。類(lèi)似地躺盛,如果被調(diào)用的協(xié)程拋出異常项戴,該異常也會(huì)傳導(dǎo)給調(diào)用者。實(shí)際上槽惫,只要你用 yield 表達(dá)式來(lái)調(diào)用協(xié)程(否則會(huì)阻塞)周叮,這個(gè)例子就模擬了 Stackless Python 中使用的簡(jiǎn)單的子任務(wù)(tasklet)辩撑。這只是一個(gè)非常簡(jiǎn)單的例子,但也可以使用更復(fù)雜的調(diào)度程序仿耽。(例如合冀,現(xiàn)有的 GTasklet 框架 (http://www.gnome.org/~gjc/gtasklet/gtasklets.html) 和 peak.events 框架 (http://peak.telecommunity.com/) 已經(jīng)實(shí)現(xiàn)類(lèi)似的調(diào)度功能,但大多數(shù)因?yàn)闊o(wú)法將值或異常傳給生成器项贺,而必須使用很尷尬的解決方法君躺。)
import collections

class Trampoline:
    """Manage communications between coroutines"""

    running = False

    def __init__(self):
        self.queue = collections.deque()

    def add(self, coroutine):
        """Request that a coroutine be executed"""
        self.schedule(coroutine)

    def run(self):
        result = None
        self.running = True
        try:
            while self.running and self.queue:
               func = self.queue.popleft()
               result = func()
            return result
        finally:
            self.running = False

    def stop(self):
        self.running = False

    def schedule(self, coroutine, stack=(), val=None, *exc):
        def resume():
            value = val
            try:
                if exc:
                    value = coroutine.throw(value,*exc)
                else:
                    value = coroutine.send(value)
            except:
                if stack:
                    # send the error back to the "caller"
                    self.schedule(
                        stack[0], stack[1], *sys.exc_info()
                    )
                else:
                    # Nothing left in this pseudothread to
                    # handle it, let it propagate to the
                    # run loop
                    raise

            if isinstance(value, types.GeneratorType):
                # Yielded to a specific coroutine, push the
                # current one on the stack, and call the new
                # one with no args
                self.schedule(value, (coroutine,stack))

            elif stack:
                # Yielded a result, pop the stack and send the
                # value to the caller
                self.schedule(stack[0], stack[1], value)

            # else: this pseudothread has ended

        self.queue.append(resume)
  1. 一個(gè)簡(jiǎn)單的 echo 服務(wù)器以及用蹦床原理實(shí)現(xiàn)的運(yùn)行代碼(假設(shè)存在 nonblocking_readnonblocking_write 和其它 I/O 協(xié)程开缎,該例子在連接關(guān)閉時(shí)拋出 ConnectionLost ):
# coroutine function that echos data back on a connected
# socket
#
def echo_handler(sock):
    while True:
        try:
            data = yield nonblocking_read(sock)
            yield nonblocking_write(sock, data)
        except ConnectionLost:
            pass  # exit normally if connection lost

# coroutine function that listens for connections on a
# socket, and then launches a service "handler" coroutine
# to service the connection
#
def listen_on(trampoline, sock, handler):
    while True:
        # get the next incoming connection
        connected_socket = yield nonblocking_accept(sock)

        # start another coroutine to handle the connection
        trampoline.add( handler(connected_socket) )

# Create a scheduler to manage all our coroutines
t = Trampoline()

# Create a coroutine instance to run the echo_handler on
# incoming connections
#
server = listen_on(
    t, listening_socket("localhost","echo"), echo_handler
)

# Add the coroutine to the scheduler
t.add(server)

# loop forever, accepting connections and servicing them
# "in parallel"
#
t.run()

參考實(shí)現(xiàn)

實(shí)現(xiàn)了本 PEP 中描述的所有功能的原型補(bǔ)丁已經(jīng)可用棕叫,參見(jiàn) SourceForge 補(bǔ)丁 1223381 (https://bugs.python.org/issue1223381)奕删。

該補(bǔ)丁已提交到 CVS,2005年8月 01-02伏钠。

致謝

Raymond Hettinger (PEP 288) 與 Samuele Pedroni (PEP 325) 第一個(gè)正式地提出將值或異常傳遞給生成器的想法熟掂,以及關(guān)閉生成器的能力扎拣。Timothy Delaney 建議了本 PEP 的標(biāo)題鹏秋,還有 Steven Bethard 幫忙編輯了早期的版本亡笑。另見(jiàn) PEP-340 的致謝部分仑乌。

參考文獻(xiàn)

TBD.

版權(quán)

本文檔已經(jīng)放置在公共領(lǐng)域晰甚。

源文檔:https://github.com/python/peps/blob/master/pep-0342.txt

----------------(譯文完)--------------------

相關(guān)鏈接:

PEP背景知識(shí)學(xué)習(xí)Python厕九,怎能不懂點(diǎn)PEP呢?

PEP翻譯計(jì)劃https://github.com/chinesehuazhou/peps-cn

[譯] PEP 255--簡(jiǎn)單的生成器

[譯]PEP 525--異步生成器

花下貓語(yǔ): 嘮叨幾句吧俊鱼,年前這幾周事情太多了,擠著時(shí)間好歹是又翻譯出一篇 PEP细睡。與生成器密切相關(guān)的 PEP 已經(jīng)完成 3/4溜徙,年后再譯最后一篇(PEP-380)蠢壹。當(dāng)初翻譯第一篇九巡,完全是一時(shí)興起比庄,直覺(jué)這是一件有意義的事佳窑,現(xiàn)在呢,這個(gè)念頭開(kāi)始有點(diǎn)膨脹——我竟然在 Github 上建了個(gè)翻譯項(xiàng)目净神。我深知鹃唯,自己水平實(shí)在有限坡慌,因此不求得到多少認(rèn)同吧藻三。但行好事棵帽,莫問(wèn)前程。不過(guò)弟晚,若有人幫著吆喝一聲卿城,也是極好的藻雪。

-----------------

本文原創(chuàng)并首發(fā)于微信公眾號(hào)【Python貓】勉耀,后臺(tái)回復(fù)“愛(ài)學(xué)習(xí)”,免費(fèi)獲得20+本精選電子書(shū)至壤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末像街,一起剝皮案震驚了整個(gè)濱河市镰绎,隨后出現(xiàn)的幾起案子畴栖,更是在濱河造成了極大的恐慌吗讶,老刑警劉巖恋捆,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膜毁,死亡現(xiàn)場(chǎng)離奇詭異星立,居然都是意外死亡绰垂,警方通過(guò)查閱死者的電腦和手機(jī)火焰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)绒怨,“玉大人南蹂,你說(shuō)我怎么就攤上這事念恍。” “怎么了疗疟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵策彤,是天一觀的道長(zhǎng)店诗。 經(jīng)常有香客問(wèn)我必搞,道長(zhǎng)囊咏,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮户辞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刃榨。我一直安慰自己枢希,他們只是感情好朱沃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著搬卒,像睡著了一般契邀。 火紅的嫁衣襯著肌膚如雪坯门。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天畜号,我揣著相機(jī)與錄音简软,去河邊找鬼述暂。 笑死畦韭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的察郁。 我是一名探鬼主播转唉,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赠法,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼砖织!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起新锈,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤壕鹉,失蹤者是張志新(化名)和其女友劉穎晾浴,沒(méi)想到半個(gè)月后牍白,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體脊凰,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年帕胆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了般渡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驯用。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奔浅,死狀恐怖量窘,靈堂內(nèi)的尸體忽然破棺而出涡驮,到底是詐尸還是另有隱情眷射,我是刑警寧澤钠怯,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站断国,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏稳衬。R本人自食惡果不足惜薄疚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一街夭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呈枉,春花似錦猖辫、人聲如沸啃憎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)耳幢。三九已至欧啤,卻和暖如春邢隧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背按摘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工炫贤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留付秕,地道東北人询吴。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爆捞,于是被迫代替她去往敵國(guó)和親嵌削。 傳聞我的和親對(duì)象是個(gè)殘疾皇子望艺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容