當前位置:成語大全網 - 新華字典 - 學 Python 怎能不知 yield?

學 Python 怎能不知 yield?

理解yield 的 generator 概念,首先以壹個常見的編程題目來展示 yield 的概念。

斐波那契(Fibonacci)數列是壹個非常簡單的遞歸數列,除第壹個和第二個數外,任意壹個數都可由前兩個數相加得到。用計算機程序輸出斐波那契數列的前 N 個數是壹個非常簡單的問題,有些 Python 基礎的小夥伴都可以輕易寫出如下函數:

第 1 版本:簡單輸出斐波那契數列前 N 個數

執行以上代碼,我們可以得到如下輸出:

輸出結果是沒有問題的,但是版本 1 中的寫法是直接在 createNum 函數中用 print 打印數字會導致該函數可復用性較差,因為 createNum 函數返回 None,其他函數無法獲得該函數生成的數列。

要提高 createNum 函數的可復用性,最好不要直接打印出數列,而是返回壹個 List。以下是 createNum 函數改寫後的第二個版本:

第 2 版本:輸出斐波那契數列前 N 個數

該版本中 createNum 函數返回的 List的結果如下:

改寫後的 createNum 函數通過返回 List 能滿足復用性的要求,但是與此同時也會存在壹個明顯的問題是:該函數在運行中占用的內存會隨著參數 count 的增大而增大,如果要控制內存占用,最好不要用 List 來保存中間結果,而是通過 iterable 對象來叠代。在每次叠代中返回下壹個數值,如此:內存空間占用很小。因為是直接返回壹個 iterable 對象。

第 3 版本:使用 yield 輸出斐波那契數列前 N 個數

也可以手動調用 createNum(5) 的 next() 方法(因為 createNum(5) 是壹個 generator 對象,該對象具有 next() 方法),這樣我們就可以更清楚地看到 createNum 的執行流程:

第 4 版本:執行流程

運行以上代碼,結果輸出如下:

由輸出結果可發現在執行第 6 個 print(next(num)) 時拋出壹個 StopIteration 的異常,是因為在第 5 個 print(next(num)) 執行完時函數已經結束,再執行第 6 個print(next(num))時,generator 自動拋出 StopIteration 異常,表示叠代完成。在 for 循環裏,無需處理 StopIteration 異常,循環會正常結束。

簡單地講,yield 的作用就是把壹個函數變成壹個 generator,帶有 yield 的函數不再是壹個普通函數,Python 解釋器會將其視為壹個 generator,調用 createNum(5) 不會執行 createNum 函數,而是返回壹個 iterable 對象!

在 for 循環執行時,每次循環都會執行 createNum 函數內部的代碼,執行到 yield b 時,createNum 函數就會返回壹個叠代值,下次叠代時,代碼從 yield b 的下壹條語句繼續執行,而函數的本地變量看起來和上次中斷執行前是完全壹樣的,於是函數繼續執行,直到再次遇到 yield。

壹個帶有 yield 的函數就是壹個 generator,它和普通函數不同,生成壹個 generator 看起來像函數調用,但不會執行任何函數代碼,直到對其調用 next()(在 for 循環中會自動調用 next())才開始執行。雖然執行流程仍按函數的流程執行,但每執行到壹個 yield 語句就會中斷,並返回壹個叠代值,下次執行時從 yield 的下壹個語句繼續執行。看起來就好像壹個函數在正常執行的過程中被 yield 中斷了數次,每次中斷都會通過 yield 返回當前的叠代值。

yield 的好處是顯而易見的,把壹個函數改寫為壹個 generator 就獲得了叠代能力,比起用類的實例保存狀態來計算下壹個 next() 的值,不僅代碼簡潔,而且執行流程異常清晰。