Python 迭代器 生成器

迭代器(Iterator)


可以直接作用於for循環的對象,統稱爲可迭代對象:Iterable。
可以被next()函數調用並不斷返回下一個值的對象,稱爲迭代器:Iterator。

Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它纔會計算。
Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
迭代器僅僅在迭代到某個元素時才計算該元素,而在這之前或之後,元素可以不存在或者被銷燬。這個特點使得它特別適合用於遍歷一些巨大的或是無限的集合,比如幾個G的文件,或是斐波那契數列等等。

與一般的序列類型(list, tuple等)有什麼區別:
它一次只返回一個數據項,佔用更少的內存。
但它需要記住當前的狀態,以便返回下一數據項。它是一個有着next()方法的對象。
而序列類型則保存了所有的數據項,它們的訪問是通過索引進行的。

對於原生支持隨機訪問的數據結構(如tuple、list),迭代器和經典for循環的索引訪問相比並無優勢,反而丟失了索引值(可以使用內建函數enumerate()找回這個索引值)。

迭代器有兩個基本的方法

next方法:返回迭代器的下一個元素
__iter__方法:返回迭代器對象本身

迭代器:僅是一容器對象,它實現了迭代器協議。

下面用生成斐波那契數列爲例子,說明爲何用迭代器。
def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        print b 
        a, b = b, a + b 
        n = n + 1
直接在函數fab(max)中用print打印會導致函數的可複用性變差,因爲fab返回None。其他函數無法獲得fab函數返回的數列。
def fab(max): 
    L = []
    n, a, b = 0, 0, 1 
    while n < max: 
        L.append(b) 
        a, b = b, a + b 
        n = n + 1
    return L
滿足了可複用性的需求,但是佔用了內存空間,最好不要。
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


>>> for key in Fabs(5):
    print key
      
1
1
2
3
5
Fabs 類通過 next() 不斷返回數列的下一個數,內存佔用始終爲常數


生成器


帶有 yield 的函數在 Python 中被稱之爲 generator(生成器),幾個例子說明下(還是用生成斐波那契數列說明)
可以看出代碼3遠沒有代碼1簡潔,生成器(yield)既可以保持代碼1的簡潔性,又可以保持代碼3的效果

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

        n = n + 1


>>> 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。看起來就好像一個函數在正常執行的過程中被 yield 中斷了數次,每次中斷都會通過 yield 返回當前的迭代值。

如果在執行過程中 return,則直接拋出 StopIteration 終止迭代。


文件讀取


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
如果直接對文件對象調用 read() 方法,會導致不可預測的內存佔用。
好的方法是利用固定長度的緩衝區來不斷讀取文件內容。通過 yield,我們不再需要編寫讀文件的迭代類,就可以輕鬆實現文件讀取。


類型判斷


判斷一個函數是否是一個特殊的 generator 函數
 >>> from inspect import isgeneratorfunction 
 >>> isgeneratorfunction(fab) 
 True
要注意區分 fab 和 fab(5),fab 是一個 generator function,而 fab(5) 是調用 fab 返回的一個 generator,好比類的定義和類的實例的區別:
 >>> 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


使用isinstance()判斷一個對象是否是Iterable對象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True>>> isinstance({}, Iterable)
True>>> isinstance('abc', Iterable)
True>>> isinstance((x for x in range(10)), Iterable)
True>>> isinstance(100, Iterable)

False


使用isinstance()判斷一個對象是否是Iterator對象:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator) #是(x for x in range(10))不是[x for x in range(10)]
True>>> isinstance([], Iterator)
False>>> isinstance({}, Iterator)
False>>> isinstance('abc', Iterator)

False



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章