[Python 學習]2.5版yield之學習心得

來源:互聯網
上載者:User

(轉)[Python 學習]2.5版yield之學習心得

 

在 shhgs 發布了關於《 Py 2.5 what's new 之 yield》之後,原來我不是特別關注 yield 的用法,因為對於2.3中加入的yield相對來說功能簡單,它是作為一個 generator 不可缺少的一條語句,只要包含它的函數即是一個 generator 。但在2.3中,generator 不能重入,不能在運行過程中修改,不能引發異常,你要麼是順序調用,要麼就建立一個新的 generator。而且 generator 中的 yield 只是一個語句。但到了 2.5 版之後,情況發生了很在的變化。

在 shhgs 的文章中對於 yield 並沒有做太多的描述,也因此讓我在理解上產生了許多問題,於是我仔細地研究了 What's new 和 PEP 342 文檔,有了一些體會,描述在下面。

這裡不說為什麼要對 yield 進行修改,只說功能。

1. yield 成為了運算式,它不再是語句,但可以放在單獨的行上。原文:

Redefine "yield" to be an expression, rather than a statement. The current yield statement would become a yield expression whose value is thrown away.

可以看到,如果你還是寫成語句形式的話,其實還是一個運算式,只是它的值被扔掉了。

那麼一個 yield 運算式可以這樣寫:

x = yield i
y = x + (yield x)

那麼這種機制到底是如何工作的呢?在2.3版很容易理解,你完全可以把 yield 語句理解為一個 "return" 語句,只不過 "return" 完後,函數並不結束,而是斷續運行,直到再次遇到 yield 語句。那麼到了 2.5 版不僅僅是一個 "return" 語句那麼簡單了,讓我們看完下面關於 send() 的說明再描述它吧。

2. 增加了 send(msg) 方法,因此你可以使用它向 generator 發送訊息。原文:

Add a new send() method for generator-iterators, which resumes the generator and "sends" a value that becomes the result of the current yield-expression. The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.

執行一個 send(msg) 會恢複 generator 的運行,然後發送的值將成為當前 yield 運算式的傳回值。然後 send() 會返回下一個被 generator yield 的值,如果沒有下一個可以 yield 的值則引發一個異常。

那麼可以看過這其實包含了一次運行,從將msg賦給當前被停住的 yield 運算式開始,到下一個 yield 語句結束,然後返回下一個yield語句的參數,然後再掛起,等待下一次的調用。理解起來的確很複雜,不知道你明白了沒有。

那麼讓我們開始想象一下,把 yield 轉變為易於理解的東西吧。

我們可以把 yield 想象成下面的虛擬碼:

x = yield i ==> put(i); x = wait_and_get()

可以看到,可以理解為先是一個 put(i),這個 i 就是 yield 運算式後面的參數,如果 yield 沒有參數,則表示 None。它表示將 i 放到一個全域緩衝區中,相當於返回了一個值。

wait_and_get() 可以理解為一個阻塞調用,它等待著外界來喚醒它,並且可以返回一個值。

經過這種轉換就容易理解多了。讓我們來看一個例子:

>>> def g():
    print 'step 1'
    x = yield 'hello'
    print 'step 2', 'x=', x 
    y = 5 + (yield x)
    print 'step 3', 'y=', y 

很簡單,每執行一步都顯示一個狀態,並且列印出相關變數的值,讓我們執行一下看一看。

>>> f = g()
>>> f.next()
step 1
'hello'

看見什麼了。當我們執行 next() 時,代碼執行到 x = yield 'hello' 就停住了,並且返回了 yield 後面的 'hello'。如果我們把上面的程式替換成虛擬碼看一看是什麼樣子:

def g():
    print 'step 1'
    put('hello')    #x = yield 'hello'
    x = wait_and get()
    print 'stpe 2', 'x=', x
    put(x)
    y = 5 + wait_and_get()
    print 'step 3', 'y=', y

可以從虛擬碼看出,第一次調用 next() 時,先返回一個 'hello', 然後程式掛起在 x = wait_and_get() 上,與我們執行的結果相同。

讓我們繼續:

>>> f.send(5)
step 2 x= 5
5

這次我們使用了 send(5) 而不是 next() 了。要注意 next() 在 2.5 中只算是 send(None) 的一種表現方式。正如虛擬碼示範的,send()一個值,先是啟用 wait_and_get() ,並且通過它返回 send(5) 的參數5,於是 x 的值是 5,然後列印 'step 2',再返回 x 的值5,然後程式掛起在 y = 5 + wait_and_get() 上,與運行結果一致。

如果我們繼續:

>>> f.send(2)
step 3 y= 7

Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    f.send(2)
StopIteration

可以看到先是啟用 wait_and_get(),並且通過它返回 send(2) 的參數 2,因此 y 的值是 7,然後執行下面的列印語句,但因為後面沒有下一個 yield 語句了,因此程式無法掛起,於是就拋出異常來。

從上面的虛擬碼的樣本和運行結果的分析,我想你應該對 yield 比較清楚了。還有一些要注意的:

  • next()相當於send(None)
  • yield後面沒有參數表示返回為None

在文檔中有幾句話很重要:

Because generator-iterators begin execution at the top of the generator's function body, there is no yield expression to receive a value when the generator has just been created. Therefore, calling send() with a non-None argument is prohibited when the generator iterator has just started, and a TypeError is raised if this occurs (presumably due to a logic error of some kind). Thus, before you can communicate with a coroutine you must first call next() or send(None) to advance its execution to the first yield expression.

意思是說,第一次調用時要麼使用 next() ,要麼使用 send(None) ,不能使用 send() 來發送一個非 None 的值,原因就是第一次沒有一個 yield 運算式來接受這個值。如果你轉為虛擬碼就很好理解。以上例來說明,轉換後第一句是一個 put() 而不是wait_and_get(),因此第一次執行只能返回,而不能接受資料。如果你真的發送了一個非 None 的值,會引發一個 TypeError 的異常,讓我們試一試:

>>> f = g()
>>> f.send(5)

Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    f.send(5)
TypeError: can't send non-None value to a just-started generator

看到了吧,果然出錯了。

3. 增加了 throw() 方法,可以用來從 generator 內部來引發異常,從而控制 generator 的執行。實驗一下:

>>> f = g()
>>> f.send(None)
step 1
'hello'
>>> f.throw(GeneratorExit)

Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    f.throw(GeneratorExit)
  File "<pyshell#6>", line 3, in g
    x = yield 'hello'
GeneratorExit
>>> f.send(5)

Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    f.send(5)
StopIteration

可以看出,第一次執行後,我執行了一個f.throw(GeneratorExit),於是這個異常被引發。如果再次執行f.send(5),可以看出 generator 已經被停止了。GeneratorExit 是新增加的一個異常類,關於它的說明:

A new standard exception is defined, GeneratorExit, inheriting from Exception. A generator should handle this by re-raising it (or just not catching it) or by raising StopIteration.

可以看出,增加它的目的就是讓 generator 有機會執行一些退出時的清理工作。這一點在 PEP 342 後面的 thumbnail 的例子中用到了。

4. 增加了 close 方法。它用來關閉一個 generator ,它的虛擬碼如下(從文檔中抄來):

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

因此可以看出,首先向自身引發一個 GeneratorExit 異常,如果 generator 引發了 GeneratorExit 或 StopIteration 異常,則關閉成功。如果 generator 返回了一個值,則引發 RuntimeError 異常。如果是其它的異常則不作處理,相當於向上層繁殖,由上層代碼來處理。關於它的例子在 PEP 342 中的 thumbnail 的例子中也有描述。

還有其它幾點變化,不再做更深入的描述。

關於 PEP 342 中的例子也很值得玩味。簡單說一些,其實我也些也不是很懂也就是明白個大概其吧。

文檔中一共有4個例子,其實是兩個例子構成。

1,2兩個例子完成了一個 thunmbnail 的處理。第一個例子 consumer 其實是一個 decorator ,它實現了對一個 generator 的封裝,主要就是用來調用一次 next() 。為什麼,因為這樣調一次下一次就可以使用 send() 一個非 None 的值了,這樣後面的代碼在使用 generator 可以直接使用 send() 非 None 值來處理了。第二個例子完成對一系列的圖片的縮圖的處理。這裡每個圖片的處理做成了一個 generator,對於圖片檔案的處理又是一個頂層的 generator ,在這個頂層的 generator 來調用每個圖片處理的 generator。同時這個例子還實現了當異常退出時的一種保護工作:處理完正在處理的圖片,然後退出。

3,4兩個例子完成了一個 echo 伺服器的示範。3完成了一個調度器,4是在3的基礎上將listen處理和socket聯通後的handle處理都轉為可調度的 generator ,在調度器中進行調度。同時可以看到 socket 使用的是非阻塞的處理。

通過以上的學習,我深深地感受到 yield 的確很精巧,這一點還是在與 shhgs 語音交流之後才有更深的體會,許多東西可以通過 generator 表現得更優美和精巧,是一個非常值得玩味的東西。以至於 shhgs 感覺到在 2.5 中 yield 比 with 的意義要大。希望大家一同體會。

不過說實在的,yield 的東西的確有些難於理解,要仔細體會才行。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.