python
下面是一個使用生成器實現的,求平均值的函數函數
def averager1(): """ 使用yield接收數值,並求平均值 :return: """ count = 0 total = 0.0 average = 0.0 while True: value = yield average count += 1 total += value average = total/count avg1 = averager1() # 預激活協程,程序執行到yield出暫停,產出average,輸出0.0 print(next(avg1)) # 0.00 # 向協程發送數字 print(avg1.send(10)) # 10.0 print(avg1.send(20)) # 15.0 print(avg1.send(30)) # 20.0
學習
接着,調用者須要將貨物(數據)運輸給生成器,那麼就是從新讓小車把貨物運送給生成器(調用生成器的.send()方法)。生成器接收到yield小車運輸過來的貨物以後(value = yield
),總之能夠開始生產、銷售或者其餘事情(求平均值)。當生成器這些事情都作完後,又從新將小車開回到調用者這一方,並暫停,等待接收調用者的下一個指令,如此往返。spa
code
首先,調用生成器函數,建立一個生成器對象avg。而後對生成器對象進行預激活,這裏的預激活指的是讓生成器對象執行到yield除,而後會暫停。由於生成器對象只有在yield除暫停時,才能接收到調用者經過send方法發送給生成器的值,因此沒有進行預激活的生成器對象沒法正常工做。協程
咱們知道,python的賦值語句,是從右向左執行的,因此在這裏例子中,yield average
會先執行,將average產出。對象
這個時候程序的控制器轉交給調用者,繼續執行print語句,因此print(next(avg))
會輸出yield產出的值,即average,輸出0.00。blog
生成器的調用者繼續往下執行,調用生成器的send方法,將10發送給生成器對象。開頭說過,調用生成器對象的send方法,發送的數據,會賦值給yield左邊的變量。在這個例子中,value = yield average, 暫時先把yield右側的average忽略掉,只看value = yield,能夠理解爲,將yield獲取到了調用者經過send方法發送給生成器的值,而後把這個值賦值給左側的變量value,接着是簡單的計算平均值,這樣就實現了調用者給生成器對象發送數據。generator
注意生成器內部有個while的無限循環,生成器內部的代碼會繼續執行,直到再次遇到yield,程序暫停,再次把average的值產出,並將程序的控制器再次轉交回調用者。it
這個時候調用者繼續執行print語句,輸出average的值:10.0。後面的幾回send也是同樣的效果。
以前的例子,while True
會致使生成器對象無限循環,每次都會在yield除暫停,產出平均值average,並等待接收調用者再次經過send方法傳入的新值。
若是須要終止生成器對象的無限循環,能夠用三種方式:
發送哨符終止循環
調用生成器的.throw()方法終止循環
調用生成器的.close()方法終止循環
從最簡單的發送哨符終止循環開始,簡單的說,就是發送一個特定的值給生成器對象,當生成器獲取到這個值時,就經過break語句退出while循環。
def averager2(): """ 使用yield接收數值,並求平均值 相對於上面的例子,增長了使協程退出的哨符 :return: """ count = 0 total = 0.0 average = 0.0 while True: value = yield average # 當value爲None時,退出循環 if value is None: break count += 1 total += value average = total/count avg2 = averager2() # 預激活協程,由於yield右邊沒有變量,因此不會產出值 print(next(avg2)) # 0.0 # 向協程發送數字 print(avg2.send(10)) # 10.0 print(avg2.send(20)) # 15.0 print(avg2.send(30)) # 20.0 # 生成器循環終止時會拋出StopIteration # 因此作一個異常捕獲 try: avg2.send(None) except StopIteration: pass
上面的生成器函數,作了一個簡單的判斷,當value爲None時,就執行break語句退出生成器對象的循環。生成器循環終止時會拋出StopIteration,這個也會做爲後面生成器返回值的途徑。
調用生成器的.throw()方法,會將異常發送給生成器,生成器的處理規則以下:
生成器在yield處暫停時,會接收到throw方法傳入的異常
若是生成器能正確處理傳入的異常,那麼生成器的代碼會繼續執行,yield產出右側的值(若是右側有值的話),並做爲調用者調用生成器.throw()方法的返回值
若是生成器不能處理傳入的異常,那麼生成器的代碼會停止運行,並將異常向上冒泡,再次發給調用者
看一個例子
# 對第一個函數averager1進行修改,增長處理ValueError的代碼 def averager3(): """ 使用yield接收數值,並求平均值 對第一個函數averager3進行修改,增長處理ValueError的代碼 :return: """ count = 0 total = 0.0 average = 0.0 while True: try: value = yield average count += 1 total += value average = total/count except ValueError: # 若是捕獲到ValueError,什麼都不作 # 這樣生成器會繼續循環,直到再次遇到yield暫停 pass avg3 = averager3() next(avg3) print(avg3.send(10)) # 10.0 print(avg3.send(20)) # 15.0 # throw一個生成器能夠處理的異常ValueError,沒有任何影響 # 生成器會繼續運行,產出average,由於在yield處就報錯,後續的代碼沒有執行 # 因此average仍然爲15.0 # yield會將average產出,產出的值做爲調用者執行生成器的throw方法的返回值,最終輸出15.0 print(avg3.throw(ValueError)) # 15.0 # throw一個生成器不能處理的異常,生成器循環終止 try: print(avg3.throw(TypeError)) except TypeError: print('生成器沒法處理TypeError,異常向上冒泡拋出,循環終止')
不過和直接.throw(GeneratorExit)不一樣的是,經過close讓生成器拋出GeneratorExit後,生成器不能再產出任何值,不然會引起RuntimeError: generator ignored GeneratorExit。
# 對第三個函數averager3進行修改,改成捕獲GeneratorExit異常並忽略 def averager4(): """ 使用yield接收數值,並求平均值 對第三個函數averager3進行修改,改成捕獲GeneratorExit異常並忽略 :return: """ count = 0 total = 0.0 average = 0.0 while True: try: value = yield average count += 1 total += value average = total/count except GeneratorExit: # 若是捕獲到GeneratorExit,什麼都不作 # 這樣生成器會繼續循環,直到再次遇到yield # 由於調用close後不容許再次yield,因此會拋出 # RuntimeError: generator ignored GeneratorExit pass avg4 = averager4() next(avg4) print(avg4.send(10)) print(avg4.send(20)) avg4.close() # RuntimeError: generator ignored GeneratorExit
若是是直接.throw(GeneratorExit),那麼遵循上述的規範,若是生成器處理了這個異常,循環繼續;若是生成器沒法的處理這個異常,循環終止。
一般狀況下,生成器不該該捕獲這個異常,或者捕獲這個異常後應拋出StopItreation異常,不然調用方會報錯。
協程是經過拋出StopIteration來返回值,StopIteration第一個值就是異常的返回值。
def averager5(): """ 使用yield接收數值,並求平均值 修改averager2,每次yield再也不產出平均數 而是改成協程結束後再返回 :return: """ count = 0 total = 0.0 average = 0.0 while True: value = yield # 當value爲None時,退出循環 if value is None: break count += 1 total += value average = total/count return average avg5 = averager5() next(avg5) avg5.send(10) avg5.send(20) try: # 發送None,結束協程,同時捕獲StopIteration異常 avg5.send(None) except StopIteration as ex: print(ex) # 15
注:
《流暢的Python》學習筆記,部分例子來自書中,並有一些修改,便於驗證某些結論。