python 基礎系列--可迭代物件、迭代器與生成器
迭代器是 Python 最強大的功能之一,可以想像如果有個幾十 GB 的大檔案,你需要編寫程式處理其中的文字資訊,如果一次性全部讀入記憶體,估計機器會直接罷工了,但是借住可迭代物件,可以一次從硬碟讀取一小塊內容到記憶體,處理完後寫回硬碟,不斷迭代,從而節省記憶體,加快處理速度。
首先來解釋這3個概念。
(1)可迭代物件:如果一個物件定擁有 __iter__ 方法,那麼這個物件就是一個可迭代物件。這裡順便說下
for 迴圈的處理過程:在 Python 中我們經常使用 for 迴圈來對某個物件進行遍歷,此時被遍歷的這個物件就是可迭代物件,常見的有列表,元組,字典。for 迴圈開始時自動呼叫可迭代物件的 __iter__ 方法獲取一個迭代器,for 迴圈時自動呼叫迭代器的 next 方法獲取下一個元素,當呼叫可迭代器物件的 next 方法引發 StopIteration 異常時,結束 for 迴圈。
(2)迭代器:如果一個物件定擁有 __iter__ 方法和 __next__ 方法,那麼這個物件就是一個迭代器。
(3)生成器:生成器是一類特殊的迭代器,就是在需要的時候才產生結果,不是立即產生結果。這樣可以同時節省 CPU 和記憶體。有兩類方法實現生成器:
-
生成器函式。使用 def 定義函式,使用 yield 而不是 return 語句返回結果。yield 語句一次返回一個結果,在每個結果中間,掛起函式的狀態,以便下次從它離開的地方繼續執行。
-
生成器表示式。類似於列表推導,只不過是把一對大括號 [] 變換為一對小括號() 。但是,生成器表示式是按需產生一個生成器結果物件,要想拿到每一個元素,就需要迴圈遍歷。
三者之間的關係如下圖所示。
可迭代物件包含迭代器、序列、字典;生成器是一種特殊的迭代器。下面分別舉例說明。
建立一個迭代器
class MyListIterator(object): # 定義迭代器類,其是MyList可迭代物件的迭代器類
def __init__(self, data):
self.data = data # 上邊界
self.now = 0 # 當前迭代值,初始為0
def __iter__(self):
return self # 返回該物件的迭代器類的例項;因為自己就是迭代器,所以返回self
def __next__(self): # 迭代器類必須實現的方法
while self.now < self.data:
self.now += 1
return self.now - 1 # 返回當前迭代值
raise StopIteration # 超出上邊界,丟擲異常
類 MyListIterator 實現了 __iter__ 方法和 __next__ 方法,因此它是一個迭代器物件,由於 __iter__ 方法本返的是迭代器(本身),因此它也是可迭代物件。迭代器必然是一個可迭代物件。
下面使用3種方法遍歷迭代器 MyListIterator。
my_list = MyListIterator(5) # 得到一個可迭代物件
print("使用for迴圈來遍歷迭代器")
for i in my_list:
print(i)
my_list = MyListIterator(5) # 重新得到一個可迭代物件
print("使用next來遍歷迭代器")
print(next(my_list))
print(next(my_list))
print(next(my_list))
print(next(my_list))
print(next(my_list))
my_list = MyListIterator(5) # 重新得到一個可迭代物件
print("同時使用next和for來遍歷迭代器")
print("先使用兩次next")
print(next(my_list))
print(next(my_list))
print("再使用for,會從第三個元素2開始輸出")
for i in my_list:
print(i)
輸出結果如下:
使用for迴圈來遍歷迭代器
0
1
2
3
4
使用next來遍歷迭代器
0
1
2
3
4
同時使用next和for來遍歷迭代器
先使用兩次next
0
1
再使用for,會從第三個元素2開始輸出
2
3
4
從結果可以看出,for 迴圈實際上就是呼叫了迭代器的 __next__方法,當捕捉到 MyListIterator 異常時自動結束 for 迴圈
建立一個可迭代物件
class MyList(object): # 定義可迭代物件類
def __init__(self, num):
self.data = num # 上邊界
def __iter__(self):
return MyListIterator(self.data) # 返回該可迭代物件的迭代器類的例項
上例中物件 MyList 實現了 __iter__ 方法返回了迭代器類的例項,因此它是一個可迭代物件。遍歷操作可使用 for 迴圈,無法使用 next()。for 迴圈實質上還是呼叫 MyListIterator 的 __next__ 方法。
my_list = MyList(5) # 得到一個可迭代物件
print("使用for迴圈來遍歷可迭代物件my_list")
for i in my_list:
print(i)
my_list = MyList(5) # 得到一個可迭代物件
print("使用next來遍歷可迭代物件my_list")
print(next(my_list))
print(next(my_list))
print(next(my_list))
print(next(my_list))
print(next(my_list))
輸出結果如下:
使用for迴圈來遍歷可迭代物件my_list
0
1
2
3
4
使用next來遍歷可迭代物件my_list
print(next(my_list))
TypeError: 'MyList' object is not an iterator
從執行結果知道可迭代物件如果沒有 __next__方法,則無法通過next()進行遍歷。
建立一個生成器
像定義一般函式一樣,只不過使用 yield 返回中間結果。生成器是一種特殊的迭代器,生成器自動實現了迭代器協議,即 __iter__ 和 __next__ 方法,不需要再手動實現兩方法。建立生成器例項如下:
def myList(num): # 定義生成器
now = 0 # 當前迭代值,初始為0
while now < num:
val = (yield now) # 返回當前迭代值,
now = now + 1 if val is None else val # val為None,迭代值自增1,否則重新設定當前迭代值為val
遍歷生成器:
my_list = myList(5) # 得到一個生成器物件
print("for 迴圈遍歷生成器myList")
for i in my_list:
print(i)
my_list = myList(5) # 得到一個生成器物件
print("next遍歷生成器myList")
print(next(my_list)) # 返回當前迭代值值
print(next(my_list)) # 返回當前迭代值值
print(next(my_list)) # 返回當前迭代值值
print(next(my_list)) # 返回當前迭代值值
print(next(my_list)) # 返回當前迭代值值
執行結果如下:
for 迴圈遍歷生成器myList
0
1
2
3
4
next遍歷生成器myList
0
1
2
3
4
具有 yield 關鍵字的函式都是生成器,yield 可以理解為 return,返回後面的值給呼叫者。不同的是 return 返回後,函式會釋放,而生成器則不會。在直接呼叫 next 方法或用 for 語句進行下一次迭代時,生成器會從 yield 下一句開始執行,直至遇到下一個 yield。
(完)
如果覺得這篇文章對您有幫助,請關注公眾號 somenzz 及時獲取最新訊息或推薦給需要的朋友。