迭代器(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
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
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
>>> 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
>>> 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