摘要:提議以下的新的生成器語(yǔ)法將被允許在生成器的內(nèi)部使用其中表達(dá)式作用于可迭代對(duì)象,從迭代器中提取元素。子迭代器而非生成器的語(yǔ)義被選擇成為生成器案例的合理泛化。建議如果關(guān)閉一個(gè)子迭代器時(shí),引發(fā)了帶返回值的異常,則將該值從調(diào)用中返回給委托生成器。
導(dǎo)語(yǔ): PEP(Python增強(qiáng)提案)幾乎是 Python 社區(qū)中最重要的文檔,它們提供了公告信息、指導(dǎo)流程、新功能的設(shè)計(jì)及使用說明等內(nèi)容。對(duì)于學(xué)習(xí)者來說,PEP 是非常值得一讀的第一手材料,學(xué)習(xí)中遇到的大部分難題,都能在 PEP 中找到答案或者解決思路。
我翻譯了幾篇 PEP,這么做的目的一方面是為了加強(qiáng)學(xué)習(xí),另一方面也是為了鍛煉自己的英文水平。Python 與 English,都是如此重要。翻譯能將兩者巧妙地結(jié)合起來,真是一舉兩得。
本文介紹了子生成器的語(yǔ)法,即 yield from 語(yǔ)法。其它與生成器相關(guān)的 PEP 有 3 篇,翻譯的結(jié)果附在了本文末尾。若有對(duì)翻譯感興趣的同學(xué),可在 Github 上關(guān)注下我創(chuàng)建的項(xiàng)目 peps-cn 。
PEP原文 : https://www.python.org/dev/pe...
PEP標(biāo)題: Syntax for Delegating to a Subgenerator
PEP作者: Gregory Ewing
創(chuàng)建日期: 2009-02-13
合入版本: 3.3
譯者 :豌豆花下貓(Python貓 公眾號(hào)作者)
目錄摘要
PEP接受
動(dòng)機(jī)
提議
StopIteration 的增強(qiáng)
形式語(yǔ)義
基本原理
重構(gòu)原則
結(jié)束方式
作為線程的生成器
語(yǔ)法
優(yōu)化
使用StopIteration來返回值
被拒絕的建議
批評(píng)
可選的提案
附加材料
參考資料
版權(quán)
摘要為生成器提出了一種新的語(yǔ)法,用于將部分的操作委派給其它的生成器。這使得一部分包含“yield”的代碼段,可以被分離并放置到其它生成器中。與此同時(shí),子生成器會(huì)返回一個(gè)值,交給委派生成器(delegating generator)使用。
當(dāng)一個(gè)生成器再次 yield 被另一個(gè)生成器生成的值時(shí),該語(yǔ)法還創(chuàng)造了一些優(yōu)化的可能。
PEP接受Guido 于 2011 年 6 月 26 日正式接受本 PEP。
動(dòng)機(jī)Python 的生成器是一種協(xié)程,但有一個(gè)限制,它只能返回值給直接的調(diào)用者。這意味著包含了 yield 的代碼段不能像其它代碼段一樣,被拆分并放入到多帶帶的函數(shù)中。如果做了這樣的分解,就會(huì)導(dǎo)致被調(diào)用的函數(shù)本身成為一個(gè)生成器,并且必須顯式地迭代這個(gè)生成器,以便重新 yield 它產(chǎn)生的所有值。
如果只關(guān)心生成值的過程,那么可以不費(fèi)勁地使用如下的循環(huán):
for v in g: yield v
但是,如果在調(diào)用send(),throw()和close()的情況下,要使子生成器與調(diào)用者正確地交互,就相當(dāng)困難。如后面所說,必要的代碼非常復(fù)雜,因此想要正確地處理所有特殊情況,將會(huì)非常棘手。
一種新的語(yǔ)法被提出來解決此問題。在最簡(jiǎn)單的用例中,它等同于上面的 for-循環(huán),并且可以處理生成器的所有的行為,同時(shí)還能用簡(jiǎn)單而直接的方式進(jìn)行重構(gòu)。
提議以下的新的生成器語(yǔ)法將被允許在生成器的內(nèi)部使用:
yield from
其中
此外,當(dāng)該迭代器是一個(gè)生成器時(shí),則此生成器可以執(zhí)行 return 語(yǔ)句返回一個(gè)值,而該值將成為 yield from 表達(dá)式的值。
yield from 表達(dá)式的完整語(yǔ)義可通過生成器協(xié)議來描述如下:
迭代器返回的任何值都直接傳給調(diào)用者。
使用 send() 發(fā)送給委托生成器的任何值都直接傳給迭代器。如果發(fā)送的值是 None,則調(diào)用迭代器的 __next__() 方法。如果發(fā)送的值不是 None,則調(diào)用迭代器的 send() 方法。如果調(diào)用引發(fā)了 StopIteration,則恢復(fù)委托生成器。任何其它異常都會(huì)傳遞給委托生成器。
除 GeneratorExit 以外,任何傳給委托生成器的異常都會(huì)傳給迭代器的 throw() 方法。如果調(diào)用引發(fā) StopIteration,則恢復(fù)委托生成器。任何其它異常都會(huì)傳遞給委托生成器。
如果傳給委托生成器的是 GeneratorExit 異常,或者調(diào)用委托生成器的 close() 方法,則迭代器的 close() 方法會(huì)被調(diào)用(如果有)。如果調(diào)用時(shí)出現(xiàn)異常,則會(huì)傳給委托生成器。否則的話,在委托生成器中拋出 GeneratorExit。
yield from 表達(dá)式的值是迭代器終止時(shí)引發(fā)的 StopIteration 異常的第一個(gè)參數(shù)。
生成器里的 return expr 導(dǎo)致從生成器退出時(shí)引發(fā) StopIteration(expr)。
StopIteration的增強(qiáng)功能為方便起見,StopIteration 異常被賦予了一個(gè) value 屬性,來保存它的第一個(gè)參數(shù),若無參數(shù),則為 None。
正式的語(yǔ)義本節(jié)使用 Python 3語(yǔ)法。
1、RESULT = yield from EXPR 語(yǔ)句等同于以下語(yǔ)句:
_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
2、在生成器中,return value 語(yǔ)句在語(yǔ)義上等同于 raise StopIteration(value) ,除了一點(diǎn),當(dāng)前返回的生成器中的 except 子句無法捕獲該異常。
3、 StopIteration 異常的行為就像這樣定義:
class StopIteration(Exception): def __init__(self, *args): if len(args) > 0: self.value = args[0] else: self.value = None Exception.__init__(self, *args)基本原理 重構(gòu)原則
上面提到的大多數(shù)語(yǔ)義,其背后的基本原理源于一種對(duì)生成器代碼進(jìn)行重構(gòu)的愿望。即希望可以將包含一個(gè)或多個(gè) yield 表達(dá)式的代碼段,分離進(jìn)一個(gè)多帶帶的函數(shù)中(使用常規(guī)手段來處理作用域范圍內(nèi)的變量引用,等等),并通過 yield from 表達(dá)式來調(diào)用該函數(shù)。
在合理可行的情況下,這種復(fù)合而成的生成器的行為應(yīng)該跟原始的非分離的生成器完全相同,包括調(diào)用 __next __() 、send()、throw() 和 close() 。
子迭代器(而非生成器)的語(yǔ)義被選擇成為生成器案例的合理泛化(generalization)。
所提出的語(yǔ)義在重構(gòu)方面具有如下限制:
一個(gè)捕獲了 GenetatorExit 卻不重新拋出的代碼塊,不能在完全保留相同行為的情況下被分離出去。
如果將 StopIteration 異常拋進(jìn)了委托生成器中,則分離的生成器的行為跟原始代碼的行為可能會(huì)不同。
由于這些用例幾乎不存在,因此不值得為支持它們而考慮額外的復(fù)雜性。
結(jié)束方式當(dāng)在 yield from 處掛起時(shí),并且使用 close() 方法顯式地終止委托生成器時(shí),關(guān)于是否要一并終止子迭代器,存在一些爭(zhēng)議。一個(gè)反對(duì)的論據(jù)是,如果在別處存在對(duì)子迭代器的引用,這樣做會(huì)導(dǎo)致過早結(jié)束它。
對(duì)非引用計(jì)數(shù)型的 Python 實(shí)現(xiàn)的考慮,導(dǎo)致了應(yīng)該顯式地結(jié)束的結(jié)論,以便在所有類型的 Python 實(shí)現(xiàn)上,顯式地結(jié)束子迭代器與非重構(gòu)的迭代器,能具有相同的效果。
這里做的假設(shè)是,在大多數(shù)用例中,子迭代器不會(huì)被共享。在子迭代器被共享的稀有情況下,可通過一個(gè)阻塞調(diào)用 throw() 和 close() 的裝飾器來實(shí)現(xiàn),或者使用除 yield from 以外的方法來調(diào)用子迭代器。
作為線程的生成器使生成器能夠 return 值的動(dòng)機(jī),還考慮到使用生成器來實(shí)現(xiàn)輕量級(jí)的線程。當(dāng)以這種方式使用生成器時(shí),將輕量級(jí)線程的計(jì)算擴(kuò)散到許多函數(shù)上就會(huì)是合理的。人們希望能夠像調(diào)用普通函數(shù)一樣調(diào)用子生成器,傳遞給它參數(shù)并接收返回值。
使用提議的語(yǔ)法,像以下的表達(dá)式
y = f(x)
其中 f 是一個(gè)普通的函數(shù),就可以被轉(zhuǎn)化成一個(gè)委托調(diào)用
y = yield from g(x)
其中 g 是生成器。通過把 g 想象成一個(gè)普通的能被 yield 語(yǔ)句掛起的函數(shù),人們可以推斷出結(jié)果代碼的行為。
當(dāng)以這種方式把生成器作為線程使用時(shí),通常人們不會(huì)對(duì) yield 所傳入或傳出的值感興趣。但是,也有一些例子,線程可以作為 item 的生產(chǎn)者或消費(fèi)者。yield from 表達(dá)式允許線程的邏輯被擴(kuò)散到所需的盡可能多的函數(shù)中,item 的生產(chǎn)與消費(fèi)發(fā)生在任意的子函數(shù)中,并且這些 item 會(huì)自動(dòng)路由到/去它們的最終來源/目的地。
對(duì)于 throw() 與 close() ,可以合理地預(yù)期,如果從外部向線程內(nèi)拋入了一個(gè)異常,那么首先應(yīng)該在線程掛起處的最內(nèi)部的生成器中引發(fā),再?gòu)哪抢锵蛲鈧鬟f;而如果線程是從外部調(diào)用 close() 來終結(jié)的,那也應(yīng)該從最內(nèi)部往外地終止處于活動(dòng)態(tài)的生成器鏈。
語(yǔ)法所提出的特定語(yǔ)法被選中,像它的含義所暗示,并沒有引入任何新的關(guān)鍵詞,且清晰地突出了它與普通 yield 的不同。
優(yōu)化當(dāng)存在一長(zhǎng)串生成器時(shí),使用專門的語(yǔ)法就為優(yōu)化提供了可能性。這種生成器鏈可能存在,例如,當(dāng)遞歸遍歷樹結(jié)構(gòu)時(shí)。在鏈上傳遞 __next__() 的調(diào)用與 yield 返回值,可能造成 O(n) 開銷,最壞情況下會(huì)是 O(n**2)。
可能的策略是向生成器對(duì)象添加一個(gè)槽(slot)來保存委派給它的生成器。當(dāng)在生成器上調(diào)用 __next__() 或 send() 時(shí),首先檢查該槽,如果非空,則它引用的生成器將會(huì)被激活。如果引發(fā)了 StopIteration,該槽會(huì)被清空,并且主生成器會(huì)被激活。
這將減少一系列 C 函數(shù)調(diào)用的委托開銷,并不涉及 Python 代碼的執(zhí)行。一種可能的增強(qiáng)方法是在循環(huán)中遍歷整個(gè)生成器鏈,并直接激活最后一個(gè)生成器,盡管 StopIteration 的處理會(huì)比較復(fù)雜。
使用StopIteration來返回值有多種方法可以將生成器的返回值傳回。也有一些替代的方法,例如將其存儲(chǔ)為生成器-迭代器對(duì)象的屬性,或?qū)⑵渥鳛樽由善鞯?close() 方法的調(diào)用值返回。然而,本 PEP 提議的機(jī)制很有吸引力,有如下理由:
使用泛化的 StopIteration 異常,可以使其它類型的迭代器輕松地加入?yún)f(xié)議,而不必增加額外的屬性或 close() 方法。
它簡(jiǎn)化了實(shí)現(xiàn),因?yàn)樽由善鞯姆祷刂底兊每捎玫狞c(diǎn)與引發(fā)異常的點(diǎn)相同。延遲到任意時(shí)間都需要在某處存儲(chǔ)返回值。
被拒絕的建議一些想法被討論并且拒絕了。
建議:應(yīng)該有一些方法可以避免對(duì)__next__() 的調(diào)用,或者用帶有指定值的 send() 調(diào)用來替換它,目的是支持對(duì)生成器作裝飾,以便可以自動(dòng)地執(zhí)行初始的 __next__() 。
決議:超出本提案的范圍。這種生成器不該與 yield from 一起使用。
建議:如果關(guān)閉一個(gè)子迭代器時(shí),引發(fā)了帶返回值的 StopIteration 異常,則將該值從 close() 調(diào)用中返回給委托生成器。
此功能的動(dòng)機(jī)是為了通過關(guān)閉生成器,傳信號(hào)給傳入生成器的最后的值。被關(guān)閉的生成器會(huì)捕獲 GeneratorExit ,完成其計(jì)算并返回一個(gè)結(jié)果,該結(jié)果最終成為 close() 調(diào)用的返回值。
決議:close() 與 GeneratorExit 的這種用法,將與當(dāng)前的退出(bail-out)與清理機(jī)制的角色不兼容。這要求在關(guān)閉子生成器后、關(guān)閉一個(gè)委托生成器時(shí),該委托生成器可以被恢復(fù),而不是重新引發(fā) GeneratorExit。但這是不可接受的,因?yàn)檎{(diào)用 close() 進(jìn)行清理的意圖,無法保證委托生成器能正確地終止。
通過其它方式,可以更好地處理向消費(fèi)者告知(signal)最后的值的問題,例如發(fā)送一個(gè)哨兵值(sentinel value)或者拋入一個(gè)被生產(chǎn)者與消費(fèi)者都認(rèn)可的異常。然后,消費(fèi)者可以檢查該哨兵或異常,通過完成其計(jì)算并正常地返回,來作響應(yīng)。這種方案在存在委托的情況下表現(xiàn)正確。
建議:如果 close() 不返回值,如果出現(xiàn) StopIteration 中帶有非 None 的值,則拋出一個(gè)異常。
決議:沒有明確的理由如此做。忽略返回值在 Python 中的任何其它地方,都不會(huì)被視為錯(cuò)誤。
批評(píng)根據(jù)本提案,yield from 表達(dá)式的值將以跟普通 yield 表達(dá)式非常不同的方式得出。這意味著其它不包含 yield 表達(dá)式的語(yǔ)法可能會(huì)更合適,但到目前為止,還沒有提出可接受的替代方案。被拒絕的替代品包括 call、delegate 和 gcall。
有人提議,應(yīng)該使用子生成器中除 return 以外的某些機(jī)制,來處理 yield from 表達(dá)式的返回值。但是,這會(huì)干擾將子生成器視為可掛起函數(shù)的目的,因?yàn)樗荒芟衿渌瘮?shù)一樣 return 值。
有人批評(píng),說使用異常來傳遞返回值是“濫用異常”,卻沒有任何具體的理由來證明它。無論如何,這只是一種實(shí)現(xiàn)的建議;其它機(jī)制可以在不丟失本提案的任何關(guān)鍵特性的情況下使用。
有人建議,使用與 StopIteration 不同的異常來返回值,例如 GeneratorReturn。但是,還沒有令人信服的實(shí)際理由被提出,并且向 StopIteration 添加 value 屬性減輕了從異常(該異常可能存在也可能不存在)中提取返回值的所有困難。此外,使用不同的異常意味著,與普通函數(shù)不同,生成器中不帶值的 return,將不等同于 return None 。
可選的提案之前已經(jīng)提到了類似的提議,有些語(yǔ)法使用 yield 而不是 yield from。雖然 yield 更簡(jiǎn)潔,但是有爭(zhēng)議的是,它看起來與普通的 yield 太相似了,可能在閱讀代碼時(shí)會(huì)忽視了其中的差異。
據(jù)作者所知,之前的提案只關(guān)注于 yield 產(chǎn)生值,因此遭受到了批評(píng),即他們所替代的兩行 for 循環(huán)并沒有足夠令人厭煩,不足以讓人為新的語(yǔ)法辯護(hù)。通過處理完整的生成器協(xié)議,本提案提供了更多的好處。
附加材料本提案的語(yǔ)法的一些用例已經(jīng)被提供出來,并且基于上面概括的第一個(gè)優(yōu)化的原型也已實(shí)現(xiàn)。
Examples and Implementation
可以從跟蹤器問題的 issue 11682 中獲得針對(duì) Python 3.3 實(shí)現(xiàn)的升級(jí)版本。
參考資料[1] https://mail.python.org/pipermail/python-dev/2011-June/112010.html
[2] http://www.cosc.canterbury.ac.nz/greg.ewing/python/yield-from/
[3] http://bugs.python.org/issue11682
版權(quán)本文檔已經(jīng)放置在公共領(lǐng)域。源文檔:
https://github.com/python/pep...
-------------(譯文完)-------------
相關(guān)鏈接:
PEP背景知識(shí) :學(xué)習(xí)Python,怎能不懂點(diǎn)PEP呢?
PEP翻譯計(jì)劃 :https://github.com/chinesehua...
[[譯] PEP 255--簡(jiǎn)單的生成器](https://mp.weixin.qq.com/s/vj...
[[譯] PEP 342--增強(qiáng)型生成器:協(xié)程](https://mp.weixin.qq.com/s/M7...
[[譯] PEP 525--異步生成器](https://mp.weixin.qq.com/s/fy...
公眾號(hào)【Python貓】, 專注Python技術(shù)、數(shù)據(jù)科學(xué)和深度學(xué)習(xí),力圖創(chuàng)造一個(gè)有趣又有用的學(xué)習(xí)分享平臺(tái)。本號(hào)連載優(yōu)質(zhì)的系列文章,有喵星哲學(xué)貓系列、Python進(jìn)階系列、好書推薦系列、優(yōu)質(zhì)英文推薦與翻譯等等,歡迎關(guān)注哦。PS:后臺(tái)回復(fù)“愛學(xué)習(xí)”,免費(fèi)獲得一份學(xué)習(xí)大禮包。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/43194.html
摘要:新語(yǔ)法表達(dá)式語(yǔ)句可以被用在賦值表達(dá)式的右側(cè)在這種情況下,它就是表達(dá)式。表達(dá)式必須始終用括號(hào)括起來,除非它是作為頂級(jí)表達(dá)式而出現(xiàn)在賦值表達(dá)式的右側(cè)。 showImg(https://segmentfault.com/img/bVbnQsb?w=4344&h=2418);PEP原文 : https://www.python.org/dev/pe... PEP標(biāo)題: Coroutines v...
摘要:輔之以事件循環(huán),協(xié)程可用于異步處理,尤其是在中。當(dāng)前支持的協(xié)程基于增強(qiáng)型生成器,于版本開始采用。新的特性中,異步還有兩種新用途異步內(nèi)容管理器和迭代器。 現(xiàn)在 Python 已經(jīng)支持用協(xié)程進(jìn)行異步處理。但最近有建議稱添加協(xié)程以全面完善 Python 的語(yǔ)言結(jié)構(gòu),而不是像現(xiàn)在這樣把他們作為生成器的一個(gè)類型。此外,兩個(gè)新的關(guān)鍵字———異步(async)和等待(await),都該添加到 Pyt...
閱讀 2675·2021-11-11 16:55
閱讀 718·2021-09-04 16:40
閱讀 3109·2019-08-30 15:54
閱讀 2649·2019-08-30 15:54
閱讀 2444·2019-08-30 15:46
閱讀 431·2019-08-30 15:43
閱讀 3252·2019-08-30 11:11
閱讀 3007·2019-08-28 18:17