天天看點

python中的generator

怎麼用python實作一個斐波那契數列?新手們很容易這樣寫:

def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        print b
        a, b = b, a+b
        n += 1
           

這樣可以正确的獲得斐波那契數列,但是在函數中,用print列印數字導緻函數複用性較差。因為fab函數沒有傳回值,其他函數沒法得到通過fab函數産生的數列。

針對這個問題的一個改進辦法,就是不直接列印出來,而是傳回一個List,于是可以寫成這樣:

def fab(max):
    n, a, b = 0, 0, 1
    result = list()
    while n < max:
        result.append(b)
        a, b = b, a+b
        n += 1

    return result
           

然後通過以下方式列印出list中的數字:

for n in fab(5):
    print n
           

這樣增加了函數的複用性,但随之而來另一個問題,記憶體占用會随着max的值增大而增大,如果要控制記憶體占用,不推薦使用List。

要儲存中間結果,可以通過iterable對象來疊代。例如:

for x in range(1000): pass
           

上面這段代碼會生成一個含1000個元素的list,而:

for x in xrange(1000): pass
           

則不會生成一個List,而是在在每次疊代時傳回下一個數值,記憶體占用空間很小。xrange傳回的就是一個iterable對象。

是以,針對上面的問題,可以把fab函數改進成為一個支援iterable的class。

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 += 1
            return r
        raise StopIteration()
           

Fab類通過調用next()函數不斷傳回數列中的下一個數,記憶體占用始終是一個常數。

for n in Fab(5):
    print n
           

但是這段代碼和之前的代碼比較起來,顯得沒那麼簡潔。為了儲存代碼的簡潔,又要獲得iterable的效果,yield就派上用場了。

def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a+b
        n += 1
           

這段代碼隻是把第一段代碼中的print b改成了yield b,就有了iterable的效果。調用方式為:

for n in fab(5):
    print n
           

此處,yield的作用就是把一個函數變成一個generator,帶有yield的函數不再是一個普通函數,python解釋器會将其視為一個generator,調用fab(5)不會執行fab函數,而是傳回一個可疊代對象。在for循環執行時,每次循環都會執行fab函數内部的代碼,執行到yield b時,fab函數就傳回一個疊代值,下次疊代時,就會從yield b的下一條語句繼續執行,函數中的本地變量和上次中斷執行前是完全一樣的,于是函數繼續執行,直到再次遇到yield。

可以通過手動調用fab(5)的next()方法,看到具體的執行流程。

>>> 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異常,程式會正常結束。