1. 程式人生 > >python使用yield來減少記憶體開銷

python使用yield來減少記憶體開銷

本文參考自:http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

以斐波那契數列的實現來說明這個問題:

demo1:

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
for n in fab(5):
     print n
一般來講,我們都會寫成上邊程式的樣子,這樣既實現了基本功能,又可以複用,基本符合要求了。

但是如果max是1000w或者更大呢,那麼儲存在list中將會佔據很大的記憶體。常見的情況還包括一下消耗大量記憶體的做法:

for line in open("test.txt").readlines(): 
    print line
#這種做法是將檔案的中的內容一次全部讀取到記憶體中,每次從記憶體中取出一行然後輸出,如果檔案內容太大,將消耗大量的記憶體。
for line in open("test.txt"):
     print line
#這種做好就好多了,既簡單又不節省記憶體,利用迭代器來每次讀取一行資料。
for i in range(1000): pass
#會產生一個1000個元素的list
for i in xrange(1000): pass
#xrange()產生的是一個迭代物件,而不是一個1000個元素的list, 每次迭代返回下一個數值 
上面兩個小對比,是我們平時寫程式時不是很注意的地方,但是當你讀到這之後,以後寫程式不光要考慮怎麼實現,而且要寫出更優秀的程式碼來。

然後繼續我們的斐波那契數列:

demo2:

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()

<pre class="displaycode">for n in Fab(5): 
    print n 

上面這個版本基本達到了我們的要求,實現基本功能+節省記憶體,利用函式next來實現每次輸出下一個值, 但是這麼小小的一個功能我們竟然寫了這麼多的程式碼,不夠簡潔!!!

demo3:

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b 
        a, b = b, a + b 
        n = n + 1 
<pre name="code" class="python"><pre class="displaycode">for n in fab(5): 
    print n 
簡單來講:yield 的作用就是把一個函式變成一個 generator,帶有 yield 的函式不再是一個普通函式,Python 直譯器會將其視為一個 generator,呼叫 fab(5) 不會執行 fab 函式,而是返回一個 iterable 物件!在 for 迴圈執行時,每次迴圈都會執行 fab 函式內部的程式碼,執行到 yield b 時,fab 函式就返回一個迭代值,下次迭代時,程式碼從 yield b 的下一條語句繼續執行,而函式的本地變數看起來和上次中斷執行前是完全一樣的,於是函式繼續執行,直到再次遇到 yield。(照抄的參考文章)

個人理解: 是不是就是實時呼叫的意思,什麼時候執行next什麼時候執行一次fab函式,但是上一次的值都在記憶體中儲存。而不是一次將所有的結果求出放在list中。

更多細節請參考本文一開頭給出的文章連結, 下面我再補充一下關於迭代器和生成器的理解:

生成器:生成器(Generator)是建立迭代器的簡單而強大的工具

迭代器:

    1、對於無法隨機訪問的資料結構(比如set)而言,迭代器是唯一的訪問元素的方式

    2、它不要求你事先準備好整個迭代過程中所有的元素。迭代器僅僅在迭代至某個元素時才計算該元素,而在這之前或之後,元素可以不存在或者被銷燬。這個特點使得它特別適合用於遍歷一些巨大的或是無限的集合,比如幾個G的檔案,或是斐波那契數列等等。


具體的理解以及內部是如何工作的,大體可以參照:http://www.jb51.net/article/73939.htm

如果想更身體的瞭解迭代協議等內容,可以自行學習。