可迭代對象和迭代器

for 循環 是我們在 Python 裏非常常用的一個語法,但你有沒有思考過 for 循環是怎樣實現的?

如果你以前接觸過 C++,應該會知道類似 for (int i = 0; i < 100; i++) 這樣的寫法,它定義了 循環的執行條件 i < 100 以及 每次循環結束後執行的語句 i++,而 for 本身只起到讓代碼重複執行的作用,並沒有什麼額外的功能。這在 Python 中其實更像是 while 循環:



 i = 0
while i < 100:
    # 執行循環代碼
    i += 1

但 Python 裏的 for 循環卻不一樣。使用 for 時,我們沒有額外指定結束條件,也不需要一個用來計數的數值,甚至可以通過一個字符串進行循環。之所以可以這樣,是因爲 Python 中的 迭代器(Iterator) 以及 可迭代對象(Iterable)

如果一個對象定義了 __iter____next__ 兩個方法, 它就是一個迭代器 。對於迭代器來說,__iter__ 返回的是它自身 self__next__ 則是 返回迭代器中的下一個值 ,如果沒有值了則拋出一個 StopIteration 的異常。關於這點,你可以想象成一個只進不退的標記位,每次調用 __next__,就會將標記往後移一個元素並返回,直到結束。

有了迭代器的概念之後,如果一個對象定義了 __iter__ 和方法,返回一個迭代器對象,那麼 它就是一個可迭代的對象

從表現上來說,一個對象 可迭代 ,那麼它就可以被 for 循環使用。比如我們經常用到的 list、dict、str 等類型,都是可迭代的,所以也就可以通過 for 循環進行遍歷,或者更準確的說:被迭代。

有一點繞,我們再來理一理迭代器(Iterator)和可迭代(Iterable)這兩個的差別:

  1. 一個 迭代器一定是可迭代對象 ,因爲它一定有 __iter__ 方法。反過來則不成立。(事實上,Iterator 就是 Iterable 的子類)
  2. 迭代器的 __iter__ 方法 返回的是自身,並不產生新實例 。而可迭代對象的 __iter__ 方法通常會生成一個新的迭代器對象。

__iter____next__ 分別對應於 Python 的內置函數 iter()next():比如 iter(aList) 就相當於 aList.__iter__()

所以關於上述兩點,我們可以有以下的例子來驗證:

迭代器和可迭代之間的繼承關係。

__iter__ 方法返回值的區別。id 相同代表是同一個實例。

明白了上述的概念之後,for 循環的實現就好理解了:

  1. 首先 for 循環會調用可迭代對象的 __iter__ 方法,獲取相應的迭代器
  2. 每次循環,將迭代器的 __next__ 方法的返回值賦值給循環變量
  3. 直到捕獲迭代器拋出的 StopIteration 異常,循環結束

再來看個例子:

思考題: 想一想爲什麼迭代器 aListIter 被 for 循環迭代第二次的時候就沒有輸出了?

既然 __next__ 方法可以自己定義,我們也可以自己實現一個迭代器。比如要 輸出一個斐波那契數列 (每一位數值都是前兩位數值之和,原題回覆關鍵字 906 ),通常的做法是循環,“高級”一點的做法是遞歸。但我們也可以直接寫一個斐波那契迭代器:



# 定義迭代器
class Fibonacci():
    def __init__(self):
        self.a = 0
        self.b = 1
    def __iter__(self):
        return self
    def __next__(self):
        # 結束條件
        if self.b > 100:
            raise StopIteration
        # 更新一次數值
        self.a, self.b = self.b, self.a + self.b
        return self.a
# 創建迭代器
fib = Fibonacci()
# 進行迭代
for f in fib:
    print(f, end=' ')

輸出:



1 1 2 3 5 8 13 21 34 55 89

這個例子中,我們並沒有保存一個序列,只是定義了一種規則,就也可以被迭代。

使用迭代器的好處在於:它是一種 延遲操作 ,即當需要用到的時候纔去產生結果。比如對於一個序列來說,如果我們要遍歷它,並不需要再一開始就把所有元素都生成好,而是只需要知道每個元素的下一個元素是什麼就可以了。這樣可以節省很多空間,尤其對於數量很大的集合來說。

如果你不懂迭代器的概念,並不影響在代碼中使用 for 循環。但瞭解之後,你會對代碼理解得更透徹,同時這也是爲我們後面要講到的 生成器 做鋪墊。

════

其他文章及回答:

如何自學Python | 新手引導 | 精選Python問答 | Python單詞表 | 人工智能 | 爬蟲 | 我用Python | requests | 計算機視覺 | 字符播放器 | 一圖學Python

歡迎搜索及關注公衆號: Crossin的編程教室

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