關於Python中的yield(轉載)

来源:http://www.cnblogs.com/xyz7885696/archive/2016/01/13/5127374.html
-Advertisement-
Play Games

您可能聽說過,帶有 yield 的函數在 Python 中被稱之為 generator(生成器),何謂 generator ?我們先拋開 generator,以一個常見的編程題目來展示 yield 的概念。如何生成斐波那契數列斐波那契(Fibonacci)數列是一個非常簡單的遞歸數列,除第一個和第二...


您可能聽說過,帶有 yield 的函數在 Python 中被稱之為 generator(生成器),何謂 generator ?

我們先拋開 generator,以一個常見的編程題目來展示 yield 的概念。

如何生成斐波那契數列

斐波那契(Fibonacci)數列是一個非常簡單的遞歸數列,除第一個和第二個數外,任意一個數都可由前兩個數相加得到。用電腦程式輸出斐波那契數列的前 N 個數是一個非常簡單的問題,許多初學者都可以輕易寫出如下函數:

清單 1. 簡單輸出斐波那契數列前 N 個數
 def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        print b 
        a, b = b, a + b 
        n = n + 1

執行 fab(5),我們可以得到如下輸出:

 >>> fab(5) 
 1 
 1 
 2 
 3 
 5

結果沒有問題,但有經驗的開發者會指出,直接在 fab 函數中用 print 列印數字會導致該函數可復用性較差,因為 fab 函數返回 None,其他函數無法獲得該函數生成的數列。

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

清單 2. 輸出斐波那契數列前 N 個數第二版
 def fab(max): 
    n, a, b = 0, 0, 1 
    L = [] 
    while n < max: 
        L.append(b) 
        a, b = b, a + b 
        n = n + 1 
    return L

可以使用如下方式列印出 fab 函數返回的 List:

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

改寫後的 fab 函數通過返回 List 能滿足復用性的要求,但是更有經驗的開發者會指出,該函數在運行中占用的記憶體會隨著參數 max 的增大而增大,如果要控制記憶體占用,最好不要用 List

來保存中間結果,而是通過 iterable 對象來迭代。例如,在 Python2.x 中,代碼:

清單 3. 通過 iterable 對象來迭代
 for i in range(1000): pass

會導致生成一個 1000 個元素的 List,而代碼:

 for i in xrange(1000): pass

則不會生成一個 1000 個元素的 List,而是在每次迭代中返回下一個數值,記憶體空間占用很小。因為 xrange 不返回 List,而是返回一個 iterable 對象。

利用 iterable 我們可以把 fab 函數改寫為一個支持 iterable 的 class,以下是第三個版本的 Fab:

清單 4. 第三個版本
 class Fab(object): 

    def __init__(self, max): 
        self.max = max 
        self.n, self.a, self.b = 0, 0, 1 

    def __iter__(self): 
        return self 

    def next(self): 
        if self.n < self.max: 
            r = self.b 
            self.a, self.b = self.b, self.a + self.b 
            self.n = self.n + 1 
            return r 
        raise StopIteration()

Fab 類通過 next() 不斷返回數列的下一個數,記憶體占用始終為常數:

 >>> for n in Fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

然而,使用 class 改寫的這個版本,代碼遠遠沒有第一版的 fab 函數來得簡潔。如果我們想要保持第一版 fab 函數的簡潔性,同時又要獲得 iterable 的效果,yield 就派上用場了:

清單 5. 使用 yield 的第四版
 def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b 
        # print b 
        a, b = b, a + b 
        n = n + 1 

'''

第四個版本的 fab 和第一版相比,僅僅把 print b 改為了 yield b,就在保持簡潔性的同時獲得了 iterable 的效果。

調用第四版的 fab 和第二版的 fab 完全一致:

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

簡單地講,yield 的作用就是把一個函數變成一個 generator,帶有 yield 的函數不再是一個普通函數,Python 解釋器會將其視為一個 generator,調用 fab(5) 不會執行 fab 函數,而是返回一個 iterable 對象!在 for 迴圈執行時,每次迴圈都會執行 fab 函數內部的代碼,執行到 yield b 時,fab 函數就返回一個迭代值,下次迭代時,代碼從 yield b 的下一條語句繼續執行,而函數的本地變數看起來和上次中斷執行前是完全一樣的,於是函數繼續執行,直到再次遇到 yield。

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

清單 6. 執行流程
 >>> f = fab(5) 
 >>> f.next() 
 1 
 >>> f.next() 
 1 
 >>> f.next() 
 2 
 >>> f.next() 
 3 
 >>> f.next() 
 5 
 >>> f.next() 
 Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
 StopIteration

當函數執行結束時,generator 自動拋出 StopIteration 異常,表示迭代完成。在 for 迴圈里,無需處理 StopIteration 異常,迴圈會正常結束。

我們可以得出以下結論:

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

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

如何判斷一個函數是否是一個特殊的 generator 函數?可以利用 isgeneratorfunction 判斷:

清單 7. 使用 isgeneratorfunction 判斷
 >>> from inspect import isgeneratorfunction 
 >>> isgeneratorfunction(fab) 
 True

要註意區分 fab 和 fab(5),fab 是一個 generator function,而 fab(5) 是調用 fab 返回的一個 generator,好比類的定義和類的實例的區別:

清單 8. 類的定義和類的實例
 >>> import types 
 >>> isinstance(fab, types.GeneratorType) 
 False 
 >>> isinstance(fab(5), types.GeneratorType) 
 True

fab 是無法迭代的,而 fab(5) 是可迭代的:

 >>> from collections import Iterable 
 >>> isinstance(fab, Iterable) 
 False 
 >>> isinstance(fab(5), Iterable) 
 True

每次調用 fab 函數都會生成一個新的 generator 實例,各實例互不影響:

 >>> f1 = fab(3) 
 >>> f2 = fab(5) 
 >>> print 'f1:', f1.next() 
 f1: 1 
 >>> print 'f2:', f2.next() 
 f2: 1 
 >>> print 'f1:', f1.next() 
 f1: 1 
 >>> print 'f2:', f2.next() 
 f2: 1 
 >>> print 'f1:', f1.next() 
 f1: 2 
 >>> print 'f2:', f2.next() 
 f2: 2 
 >>> print 'f2:', f2.next() 
 f2: 3 
 >>> print 'f2:', f2.next() 
 f2: 5
 

return 的作用

在一個 generator function 中,如果沒有 return,則預設執行至函數完畢,如果在執行過程中 return,則直接拋出 StopIteration 終止迭代。

 

另一個例子

另一個 yield 的例子來源於文件讀取。如果直接對文件對象調用 read() 方法,會導致不可預測的記憶體占用。好的方法是利用固定長度的緩衝區來不斷讀取文件內容。通過 yield,我們不再需要編寫讀文件的迭代類,就可以輕鬆實現文件讀取:

清單 9. 另一個 yield 的例子
 def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

以上僅僅簡單介紹了 yield 的基本概念和用法,yield 在 Python 3 中還有更強大的用法,我們會在後續文章中討論。

註:本文的代碼均在 Python 2.7 中調試通過

本文來源:http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1000*1000是大於65536的。如果不是需求需要,沒必要開闢如此之多的空間。因為這些空間實在棧上申請的(如果是局部變數),棧的空間是有限的並且是寶貴的,所以呢,開闢太多的空間而不適用很可能會出現記憶體泄露。 數組下標一般是int或unsignedint類型,在32位機器上足夠定義出20億以...
  • 簡單ASCII ART生成器
  • Flask是一個使用 Python 編寫的輕量級 Web 應用框架。Flask 本身只是 Werkezug 和 Jinja2 的之間的橋梁,前者實現一個合適的 WSGI 應用,後者處理模板。 當然, Flask 也綁定了一些通用的標準庫包,比如 logging 。 除此之外其它所有一切都交給擴展.....
  • 面向對象的集合:#coding:utf-8__author__ = 'similarface'class Set: ''' list實現集合,及其集合操作 ''' def __init__(self,value=[]): self.data=[] ...
  • 你是否只知道使用apache重寫來實現偽靜態,你是否為更改偽靜態的配置苦苦哀求運維,你是否知道使用PHP代碼來實現偽靜態,一切的一切都在這篇文章中
  • 題目:Follow up for problem "Populating Next Right Pointers in Each Node".What if the given tree could be any binary tree? Would your previous solution s...
  • 和c c++不一樣過程如下: 1 #-*- coding:utf-8 -*- 2 3 t = [[ 0 for i in range(5)]for j in range(5)] 4 5 for i in range(5): 6 for j in range(5): 7 t...
  • $now=time(); $secondtime=$end_time-$now;//期限時間減去現在時間 剩餘時間 $second=$secondtime % 60;//取餘得到秒數 $nowtime=floor($secondtime/60);//轉化成分鐘 $minute=$nowtime %....
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...