在 shhgs 發佈了關於《 Py 2.5 what’s new 之 yield》以後,原來我不是特別關注 yield 的用法,由於對於2.3中加入的yield相對來講功能簡單,它是做爲一個 generator 不可缺乏的一條語句,只要包含它的函數便是一個 generator 。但在2.3中,generator 不能重入,不能在運行過程當中修改,不能引起異常,你要麼是順序調用,要麼就建立一個新的 generator。並且 generator 中的 yield 只是一個語句。但到了 2.5 版以後,狀況發生了很在的變化。html
在 shhgs 的文章中對於 yield 並無作太多的描述,也所以讓我在理解上產生了許多問題,因而我仔細地研究了 What’s new 和 PEP 342 文檔,有了一些體會,描述在下面。python
這裏不說爲何要對 yield 進行修改,只說功能。shell
1. yield 成爲了表達式,它再也不是語句,但能夠放在單獨的行上。原文:express
Redefine "yield" to be an expression, rather than a statement. The current yield statement would become a yield expression whose value is thrown away.服務器
能夠看到,若是你仍是寫成語句形式的話,其實仍是一個表達式,只是它的值被扔掉了。socket
那麼一個 yield 表達式能夠這樣寫:函數
x = yield i
y = x + (yield x)post
那麼這種機制究竟是如何工做的呢?在2.3版很容易理解,你徹底能夠把 yield 語句理解爲一個 "return" 語句,只不過 "return" 完後,函數並不結束,而是斷續運行,直到再次遇到 yield 語句。那麼到了 2.5 版不只僅是一個 "return" 語句那麼簡單了,讓咱們看完下面關於 send() 的說明再描述它吧。學習
2. 增長了 send(msg) 方法,所以你可使用它向 generator 發送消息。原文:this
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= 7Traceback (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 比較清楚了。還有一些要注意的:
在文檔中有幾句話很重要:
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 的東西的確有些難於理解,要仔細體會才行。