1. 程式人生 > >python 基礎系列--可迭代物件、迭代器與生成器

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
同時使用nextfor來遍歷迭代器
先使用兩次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 及時獲取最新訊息或推薦給需要的朋友。
 somenzz 的公眾號