小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

PEP 255 :簡(jiǎn)單的生成器

 LibraryPKU 2019-02-06

(給Python開(kāi)發(fā)者加星標(biāo),提升Python技能


作者:豌豆花下貓 (本文來(lái)自作者投稿)

摘要

這個(gè) PEP 想在 Python 中引入生成器的概念,以及一個(gè)新的表達(dá)式,即 yield 表達(dá)式。

動(dòng)機(jī)

當(dāng)一個(gè)生產(chǎn)者函數(shù)在處理某些艱難的任務(wù)時(shí),它可能需要維持住生產(chǎn)完某個(gè)值時(shí)的狀態(tài),大多數(shù)編程語(yǔ)言都提供不了既舒服又高效的方案,除了往參數(shù)列表中添加回調(diào)函數(shù),然后每生產(chǎn)一個(gè)值時(shí)就去調(diào)用一下。

例如,標(biāo)準(zhǔn)庫(kù)中的tokenize.py采用這種方法:調(diào)用者必須傳一個(gè) tokeneater 函數(shù)給 tokenize() ,當(dāng) tokenize() 找到下一個(gè) token 時(shí)再調(diào)用。這使得 tokenize 能以自然的方式編碼,但程序調(diào)用 tokenize 會(huì)變得極其復(fù)雜,因?yàn)樗枰涀∶看位卣{(diào)前最后出現(xiàn)的是哪個(gè) token(s)。tabnanny.py中的 tokeneater 函數(shù)是處理得比較好的例子,它在全局變量中維護(hù)了一個(gè)狀態(tài)機(jī),用于記錄已出現(xiàn)的 token 和預(yù)期會(huì)出現(xiàn)的 token 。這很難正確地工作,而且也挺難讓人理解。不幸的是,它已經(jīng)是最標(biāo)準(zhǔn)的解決方法了。

有一個(gè)替代方案是一次性生成 Python 程序的全部解析,并存入超大列表中。這樣 tokenize 客戶端可以用自然的方式,即使用局部變量和局部控制流(例如循環(huán)和嵌套的 if 語(yǔ)句),來(lái)跟蹤其狀態(tài)。然而這并不實(shí)用:程序會(huì)變得臃腫,因此不能在實(shí)現(xiàn)整個(gè)解析所需的內(nèi)存上放置先驗(yàn)限制;而有些 tokenize 客戶端僅僅想要查看某個(gè)特定的東西是否曾出現(xiàn)(例如,future 聲明,或者像 IDLE 做的那樣,只是首個(gè)縮進(jìn)的聲明),因此解析整個(gè)程序就是嚴(yán)重地浪費(fèi)時(shí)間。

另一個(gè)替代方案是把 tokenize 變?yōu)橐粋€(gè)迭代器【注釋1】,每次調(diào)用它的 next() 方法時(shí)再傳遞下一個(gè) token。這對(duì)調(diào)用者來(lái)說(shuō)很便利,就像前一方案把結(jié)果存入大列表一樣,同時(shí)沒(méi)有內(nèi)存與“想要早點(diǎn)退出怎么辦”的缺點(diǎn)。然而,這個(gè)方案也把 tokenize 的負(fù)擔(dān)轉(zhuǎn)化成記住 next() 的調(diào)用狀態(tài),讀者只要瞄一眼 tokenize.tokenize_loop() ,就會(huì)意識(shí)到這是一件多么可怕的苦差事?;蛘呦胂笠幌?,用遞歸算法來(lái)生成普通樹(shù)結(jié)構(gòu)的節(jié)點(diǎn):若把它投射成一個(gè)迭代器框架實(shí)現(xiàn),就需要手動(dòng)地移除遞歸狀態(tài)并維護(hù)遍歷的狀態(tài)。

第四種選擇是在不同的線程中運(yùn)行生產(chǎn)者和消費(fèi)者。這允許兩者以自然的方式維護(hù)其狀態(tài),所以都會(huì)很舒服。實(shí)際上,Python 源代碼發(fā)行版中的 Demo/threads/Generator.py 就提供了一個(gè)可用的同步通信(synchronized-communication)類,來(lái)完成一般的任務(wù)。但是,這在沒(méi)有線程的平臺(tái)上無(wú)法運(yùn)用,而且就算可用也會(huì)很慢(與不用線程取得的成就比)。

最后一個(gè)選擇是使用 Python 的變種 Stackless 【注釋2-3】來(lái)實(shí)現(xiàn),它支持輕量級(jí)的協(xié)程。它與前述的線程方案有相同的編程優(yōu)勢(shì),效率還更高。然而,Stackless 在 Python 核心層存在爭(zhēng)議,Jython 也可能不會(huì)實(shí)現(xiàn)相同的語(yǔ)義。這個(gè) PEP 不是討論這些問(wèn)題的地方,但完全可以說(shuō)生成器是 Stackless 相關(guān)功能的子集在當(dāng)前 CPython 中的一種簡(jiǎn)單實(shí)現(xiàn),而且可以說(shuō),其它 Python 實(shí)現(xiàn)起來(lái)也相對(duì)簡(jiǎn)單。

以上分析完了已有的方案。其它一些高級(jí)語(yǔ)言也提供了不錯(cuò)的解決方案,特別是 Sather 的迭代器,它受到 CLU 的迭代器啟發(fā)【注釋4】;Icon 的生成器,一種新穎的語(yǔ)言,其中每個(gè)表達(dá)式都是生成器【注釋5】。它們雖有差異,但基本的思路是一致的:提供一種函數(shù),它可以返回中間結(jié)果(“下一個(gè)值”)給它的調(diào)用者,同時(shí)還保存了函數(shù)的局部狀態(tài),以便在停止的位置恢復(fù)(譯注:resum,下文也譯作激活)調(diào)用。一個(gè)非常簡(jiǎn)單的例子:

def fib():
    a, b = 01
    while 1:
       yield b
       a, b = b, a+b

當(dāng) fib() 首次被調(diào)用時(shí),它將 a 設(shè)為 0,將 b 設(shè)為 1,然后生成 b 給其調(diào)用者。調(diào)用者得到 1。當(dāng) fib 恢復(fù)時(shí),從它的角度來(lái)看,yield 語(yǔ)句實(shí)際上跟 print 語(yǔ)句相同:fib 繼續(xù)執(zhí)行,且所有局部狀態(tài)完好無(wú)損。然后,a 和 b 的值變?yōu)?1,并且 fib 再次循環(huán)到 yield,生成 1 給它的調(diào)用者。以此類推。 從 fib 的角度來(lái)看,它只是提供一系列結(jié)果,就像用了回調(diào)一樣。但是從調(diào)用者的角度來(lái)看,fib 的調(diào)用就是一個(gè)可隨時(shí)恢復(fù)的可迭代對(duì)象。跟線程一樣,這允許兩邊以最自然的方式進(jìn)行編碼;但與線程方法不同,這可以在所有平臺(tái)上高效完成。事實(shí)上,恢復(fù)生成器應(yīng)該不比函數(shù)調(diào)用昂貴。

同樣的方法適用于許多生產(chǎn)者/消費(fèi)者函數(shù)。例如,tokenize.py 可以生成下一個(gè) token 而不是用它作為參數(shù)調(diào)用回調(diào)函數(shù),而且 tokenize 客戶端可以以自然的方式迭代 tokens:Python 生成器是一種迭代器,但是特別強(qiáng)大。

設(shè)計(jì)規(guī)格:yield

引入了一種新的表達(dá)式:

yield_stmt:“yield”expression_list

yield 是一個(gè)新的關(guān)鍵字,因此需要一個(gè) future 聲明【注釋8】來(lái)進(jìn)行引入:在早期版本中,若想使用生成器的模塊,必須在接近頭部處包含以下行(詳見(jiàn) PEP 236):

from __future__ import generators

沒(méi)有引入 future 模塊就使用 yield 關(guān)鍵字,將會(huì)告警。 在后續(xù)的版本中,yield 將是一個(gè)語(yǔ)言關(guān)鍵字,不再需要 future 語(yǔ)句。

yield 語(yǔ)句只能在函數(shù)內(nèi)部使用。包含 yield 語(yǔ)句的函數(shù)被稱為生成器函數(shù)。從各方面來(lái)看,生成器函數(shù)都只是個(gè)普通函數(shù),但在它的代碼對(duì)象的 co_flags 中設(shè)置了新的“CO_GENERATOR”標(biāo)志。

當(dāng)調(diào)用生成器函數(shù)時(shí),實(shí)際參數(shù)還是綁定到函數(shù)的局部變量空間,但不會(huì)執(zhí)行代碼。得到的是一個(gè) generator-iterator 對(duì)象;這符合迭代器協(xié)議【注釋6】,因此可用于 for 循環(huán)。注意,在上下文無(wú)歧義的情況下,非限定名稱 “generator” 既可以指生成器函數(shù),又可以指生成器-迭代器(generator-iterator)。

每次調(diào)用 generator-iterator 的 next() 方法時(shí),才會(huì)執(zhí)行 generator-function 體中的代碼,直至遇到 yield 或 return 語(yǔ)句(見(jiàn)下文),或者直接迭代到盡頭。

如果執(zhí)行到 yield 語(yǔ)句,則函數(shù)的狀態(tài)會(huì)被凍結(jié),并將 expression_list 的值返回給 next() 的調(diào)用者?!皟鼋Y(jié)”是指掛起所有本地狀態(tài),包括局部變量、指令指針和內(nèi)部堆棧:保存足夠信息,以便在下次調(diào)用 next() 時(shí),函數(shù)可以繼續(xù)執(zhí)行,仿佛 yield 語(yǔ)句只是一次普通的外部調(diào)用。

限制:yield 語(yǔ)句不能用于 try-finally 結(jié)構(gòu)的 try 子句中。困難的是不能保證生成器會(huì)被再次激活(resum),因此無(wú)法保證 finally 語(yǔ)句塊會(huì)被執(zhí)行;這就太違背 finally 的用處了。

限制:生成器在活躍狀態(tài)時(shí)無(wú)法被再次激活:

>>def g():
...     i = me.next()
...     yield i
>>> me = g()
>>> me.next()
Traceback (most recent call last):
 ...
 File '<string>', line 2in g
ValueError: generator already executing

設(shè)計(jì)規(guī)格:return

生成器函數(shù)可以包含以下形式的return語(yǔ)句:

return

注意,生成器主體中的 return 語(yǔ)句不允許使用 expression_list (然而當(dāng)然,它們可以嵌套地使用在生成器里的非生成器函數(shù)中)。

當(dāng)執(zhí)行到 return 語(yǔ)句時(shí),程序會(huì)正常 return,繼續(xù)執(zhí)行恰當(dāng)?shù)?finally 子句(如果存在)。然后引發(fā)一個(gè) StopIteration 異常,表明迭代器已經(jīng)耗盡。如果程序沒(méi)有顯式 return 而執(zhí)行到生成器的末尾,也會(huì)引發(fā) StopIteration 異常。

請(qǐng)注意,對(duì)于生成器函數(shù)和非生成器函數(shù),return 意味著“我已經(jīng)完成,并且沒(méi)有任何有趣的東西可以返回”。

注意,return 并不一定會(huì)引發(fā) StopIteration :關(guān)鍵在如何處理封閉的 try-except 結(jié)構(gòu)。 如:

>>def f1():
...     try:
...         return
...     except:
...        yield 1
>>> print list(f1())
[]

因?yàn)?,就像在任何函?shù)中一樣,return 只是退出,但是:

>>> def f2():
...     try:
...         raise StopIteration
...     except:
...         yield 42
>>> print list(f2())
[42]

因?yàn)?StopIteration 被一個(gè)簡(jiǎn)單的 except 捕獲,就像任意異常一樣。

設(shè)計(jì)規(guī)格:生成器和異常傳播

如果一個(gè)未捕獲的異?!ǖ幌抻?StopIteration——由生成器函數(shù)引發(fā)或傳遞,則異常會(huì)以通常的方式傳遞給調(diào)用者,若試圖重新激活生成器函數(shù)的話,則會(huì)引發(fā) StopIteration 。 換句話說(shuō),未捕獲的異常終結(jié)了生成器的使用壽命。

示例(不合語(yǔ)言習(xí)慣,僅作舉例):

>>def f():
...     return 1/0
>>def g():
...     yield f()  # the zero division exception propagates
...     yield 42   # and we'll never get here
>>> k = g()
>>> k.next()
Traceback (most recent call last):
  File '<stdin>', line 1in ?
  File '<stdin>', line 2in g
  File '<stdin>', line 2in f
ZeroDivisionError: integer division or modulo by zero
>>> k.next()  # and the generator cannot be resumed
Traceback (most recent call last):
  File '<stdin>', line 1in ?
StopIteration
>>>

設(shè)計(jì)規(guī)格:Try/Exception/Finally

前面提過(guò),yield 語(yǔ)句不能用于 try-finally 結(jié)構(gòu)的 try 子句中。這帶來(lái)的結(jié)果是生成器要非常謹(jǐn)慎地分配關(guān)鍵的資源。但是在其它地方,yield 語(yǔ)句并無(wú)限制,例如 finally 子句、except 子句、或者 try-except 結(jié)構(gòu)的 try 子句:

>>> def f():
...     try:
...         yield 1
...         try:
...             yield 2
...             1/0
...             yield 3  # never get here
...         except ZeroDivisionError:
...             yield 4
...             yield 5
...             raise
...         except:
...             yield 6
...         yield 7     # the 'raise' above stops this
...     except:
...         yield 8
...     yield 9
...     try:
...         x = 12
...     finally:
...        yield 10
...     yield 11
>>> print list(f())
[1245891011]
>>>

示例

# 二叉樹(shù)類
class Tree:

    def __init__(self, label, left=None, right=None):
        self.label = label
        self.left = left
        self.right = right

    def __repr__(self, level=0, indent='    '):
        s = level*indent + `self.label`
        if self.left:
            s = s + '\n' + self.left.__repr__(level+1, indent)
        if self.right:
            s = s + '\n' + self.right.__repr__(level+1, indent)
        return s

    def __iter__(self):
        return inorder(self)

# 從列表中創(chuàng)建 Tree
def tree(list):
    n = len(list)
    if n == 0:
        return []
    i = n / 2
    return Tree(list[i], tree(list[:i]), tree(list[i+1:]))

# 遞歸生成器,按順序生成樹(shù)標(biāo)簽
def inorder(t):
    if t:
        for x in inorder(t.left):
            yield x
        yield t.label
        for x in inorder(t.right):
            yield x

# 展示:創(chuàng)建一棵樹(shù)
t = tree('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
# 按順序打印樹(shù)的節(jié)點(diǎn)
for x in t:
    print x,
print

# 非遞歸生成器
def inorder(node):
    stack = []
    while node:
        while node.left:
            stack.append(node)
            node = node.left
        yield node.label
        while not node.right:
            try:
                node = stack.pop()
            except IndexError:
                return
            yield node.label
        node = node.right

# 練習(xí)非遞歸生成器
for x in t:
    print x,
print
Both output blocks display:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

問(wèn)答

為什么重用 def 而不用新的關(guān)鍵字?

請(qǐng)參閱下面的 BDFL 聲明部分。

為什么用新的關(guān)鍵字yield而非內(nèi)置函數(shù)?

Python 中通過(guò)關(guān)鍵字能更好地表達(dá)控制流,即 yield 是一個(gè)控制結(jié)構(gòu)。而且為了 Jython 的高效實(shí)現(xiàn),編譯器需要在編譯時(shí)就確定潛在的掛起點(diǎn),新的關(guān)鍵字會(huì)使這一點(diǎn)變得簡(jiǎn)單。CPython 的實(shí)現(xiàn)也大量利用它來(lái)檢測(cè)哪些函數(shù)是生成器函數(shù)(盡管一個(gè)新的關(guān)鍵字替代 def 就能解決 CPython 的問(wèn)題,但人們問(wèn)“為什么要新的關(guān)鍵字”問(wèn)題時(shí),并不想要新的關(guān)鍵字)。

為什么不是其它不帶新關(guān)鍵字的特殊語(yǔ)法?

例如,為何不用下面用法而用 yield 3:

return 3 and continue
return and continue 3
return generating 3
continue return 3
return >> , 3
from generator return 3
return >> 3
return << 3
>> 3
<< 3
3

我沒(méi)有錯(cuò)過(guò)一個(gè)“眼色”吧?在數(shù)百條消息中,我算了每種替代方案有三條建議,然后總結(jié)出上面這些。不需要用新的關(guān)鍵字會(huì)很好,但使用 yield 會(huì)更好——我個(gè)人認(rèn)為,在一堆無(wú)意義的關(guān)鍵字或運(yùn)算符序列中,yield 更具表現(xiàn)力。盡管如此,如果這引起足夠的興趣,支持者應(yīng)該發(fā)起一個(gè)提案,交給 Guido 裁斷。

為什么允許用return,而不強(qiáng)制用StopIteration?

“StopIteration”的機(jī)制是底層細(xì)節(jié),就像 Python 2.1 中的“IndexError”的機(jī)制一樣:實(shí)現(xiàn)時(shí)需要做一些預(yù)先定義好的東西,而 Python 為高級(jí)用戶開(kāi)放了這些機(jī)制。盡管不強(qiáng)制要求每個(gè)人都在這個(gè)層級(jí)工作。 “return”在任何一種函數(shù)中都意味著“我已經(jīng)完成”,這很容易解讀和使用。注意,return 并不總是等同于 try-except 結(jié)構(gòu)中的 raise StopIteration(參見(jiàn)“設(shè)計(jì)規(guī)格:Return”部分)。

那為什么不允許return一個(gè)表達(dá)式?

也許有一天會(huì)允許。 在 Icon 中,return expr 意味著“我已經(jīng)完成”和“但我還有最后一個(gè)有用的值可以返回,這就是它”。 在初始階段,不強(qiáng)制使用return expr的情況下,使用 yield 僅僅傳遞值,這很簡(jiǎn)單明了。

BDFL聲明

Issue

引入另一個(gè)新的關(guān)鍵字(比如,gen 或 generator )來(lái)代替 def ,或以其它方式改變語(yǔ)法,以區(qū)分生成器函數(shù)和非生成器函數(shù)。

Con

實(shí)際上(你如何看待它們),生成器函數(shù),但它們具有可恢復(fù)性。使它們建立起來(lái)的機(jī)制是一個(gè)相對(duì)較小的技術(shù)問(wèn)題,引入新的關(guān)鍵字無(wú)助于強(qiáng)調(diào)生成器是如何啟動(dòng)的機(jī)制(生成器生命中至關(guān)重要卻很小的部分)。

Pro

實(shí)際上(你如何看待它們),生成器函數(shù)實(shí)際上是工廠函數(shù),它們就像施了魔法一樣地生產(chǎn)生成器-迭代器。 在這方面,它們與非生成器函數(shù)完全不同,更像是構(gòu)造函數(shù)而不是函數(shù),因此重用 def 無(wú)疑是令人困惑的。藏在內(nèi)部的 yield 語(yǔ)句不足以警示它們的語(yǔ)義是如此不同。

BDFL

def 留了下來(lái)。任何一方都沒(méi)有任何爭(zhēng)論是完全令人信服的,所以我咨詢了我的語(yǔ)言設(shè)計(jì)師的直覺(jué)。它告訴我 PEP 中提出的語(yǔ)法是完全正確的——不是太熱,也不是太冷。但是,就像希臘神話中的 Delphi(譯注:特爾斐,希臘古都) 的甲骨文一樣,它并沒(méi)有告訴我原因,所以我沒(méi)有對(duì)反對(duì)此 PEP 語(yǔ)法的論點(diǎn)進(jìn)行反駁。 我能想出的最好的(除了已經(jīng)同意做出的反駁)是“FUD”(譯注:縮寫(xiě)自 fear、uncertainty 和 doubt)。 如果這從第一天開(kāi)始就是語(yǔ)言的一部分,我非常懷疑這早已讓安德魯·庫(kù)奇林(Andrew Kuchling)的“Python Warts”頁(yè)面成為可能。(譯注:wart 是疣,一種難看的皮膚病。這是一個(gè) wiki 頁(yè)面,列舉了對(duì) Python 吹毛求疵的建議)。

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

當(dāng)前的實(shí)現(xiàn)(譯注:2001年),處于初步狀態(tài)(沒(méi)有文檔,但經(jīng)過(guò)充分測(cè)試,可靠),是Python 的 CVS 開(kāi)發(fā)樹(shù)【注釋9】的一部分。 使用它需要您從源代碼中構(gòu)建 Python。

這是衍生自 Neil Schemenauer【注釋7】的早期補(bǔ)丁。

腳注和參考文獻(xiàn)

[1] PEP-234, Iterators, Yee, Van Rossum

http://www./dev/peps/pep-0234/

[2] http://www./

[3] PEP-219, Stackless Python, McMillan

http://www./dev/peps/pep-0219/

[4] 'Iteration Abstraction in Sather' Murer, Omohundro, Stoutamire and Szyperski 

http://www.icsi./~sather/Publications/toplas.html

[5] http://www.cs./icon/

[6] The  concept  of  iterators  is  described  in PEP 234. 

[7] http:///nas/python/generator.diff

[8] PEP 236, Back to the future, Peters

http://www./dev/peps/pep-0236/

[9] To experiment with this implementation, check out Python from CVS according to the instructions at http:///cvs/?group_id=5470 ,Note that the std test Lib/test/test_generators.py contains many examples, including all those in this PEP.

版權(quán)信息

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

https://github.com/python/peps/blob/master/pep-0255.txt



【本文作者】


豌豆花下貓:某985高校畢業(yè)生, 兼具極客思維與人文情懷 。個(gè)人公眾號(hào)Python貓, 專注python技術(shù)、數(shù)據(jù)科學(xué)和深度學(xué)習(xí)。


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多