python黑魔法---迭代器(iterator)

發表於2017-02-03

因為Python,我見識了優雅。優雅不經在於自己使用,還在於如何設計API給別人使用。

設計 api 的時候,可以利用 python 的描述符完成很多工作,而這些描述符操作,還有一個名字就是“魔法方法”。前面我們介紹了一個裝飾器魔法,現在再來認識一下迭代器神功。

迭代器(iterator)是訪問集合內元素的一種方式,提供了一種遍歷類序列物件的方法。對於一般的序列,利用索引從0一直迭代到序列的最後一個元素。物件從集合的第一個元素開始訪問,直到所有的元素都被訪問一遍後結束。對於字典、檔案、自定義物件型別等,可以自定義迭代方式,從而實現對這些物件的遍歷。總之,迭起器就是定義了對物件進行遍歷的方式。

一些程式語言C,C++需要實現這樣的結構。另外一些高階語言python,ruby則把迭代協議在語言層面就實現了。當然,這樣的隱藏了一些細節,有時候明明已經使用了,卻渾然不知。

iter 函式

python提供了一個iter函式用來生成迭代器。這個方法有兩個引數,當只有一個引數的時候,若這個引數是一個容器,則返回這個容器的迭代器物件,若這個引數本身就是一個迭代器,則返回其自身。

iterator 的特點

迭代器都有一個next方法,每次呼叫這個方法而實現計數,當然計數不是通過索引實現,呼叫了next方法只會,迭代指標會指向下一個元素的位置。若下一個元素沒有了,則會丟擲StopIteration異常。

這樣做有什麼用呢?試想想在迭代指標還沒指到的當前元素時候,已經迭代之後的位置元素,那些元素需要計算麼?因為只有迭代到當前位置的元素時候,才開始計算元素的值。在迭代之前可以不存在,在迭代之後可以被銷燬。實現的迭代器不需要準備所遍歷的所有元素,沒錯,這就是迭代器的一大魅力,惰性計算。

for 迴圈

知道了迭代器大致的用法,我們來遍歷一個迭代器。

上面的遍歷看上去比較彆扭,更不沒有python優雅的感覺,或許,想到遍歷一個容器,for in迴圈更優雅

兩次運算的效果幾乎一樣,那麼for迴圈的機理是什麼呢。前面所言python內建實現了各種迭代協議,for迴圈就是一個很好的例子。for 迴圈的時候,首先對迴圈物件實現迭代器包裝,返回一個迭代器物件,然後每迴圈一步,就呼叫哪個迭代器物件的next方法,迴圈結束的時候,自動處理了 StopIteration這個異常。for迴圈是對迭代器進行迭代的語法糖。無處不在的語法糖。

當然實現迭代器的時候,有時候會把索引丟掉,在python可以使用內建函式enumerate獲取索引。

iterator 的定義

對於上面的 it 這個迭代器,是通過 iter方法實現的,那麼iter函式到底做了什麼呢?簡而言之,實現了迭代器協議的物件,就是迭代器。什麼事迭代器協議呢?再簡而言之,滿足下面兩個條件即可:

  • 實現了魔法方法 __iter__(),返回一個迭代物件,這個物件有一個next()方法,
  • 實現 next() 方法,返回當前的元素,並指向下一個元素的位置,當前位置已經沒有元素的時候,丟擲StopIteration異常。

前面我們迭代range(4)是從零開始,現在我們實現一個迭代器物件,可以逆序迭代的。

更復雜的遍歷邏輯,都可以在 next 方法裡構造。當然,看到了這裡,也就大概知道了迭代器的協議,也已經是python的資料結構實現了的。並且還沒見識到惰性計算。其實吧,惰性計算,python有更好的處理魔法,就是生成器,關於生成器,比迭代器神功還有效。

接下來就是用迭代器的迭代之後銷燬元素的特性,做一個練習吧。

有一個偶數項的列表 a = ["foo", 2, "bar", 4, "far", 6],希望對每兩個相鄰的兩個元素打包,是為一組, 使得結果如下是這樣的 [("foo", 2), ("bar", 4), ("far", 6)]。如果是要打包是每三個一組呢?

有很多方法可以解決,下面使用迭代器進行處理,大概程式碼如下:

對於迭代器,python還有很多高階功能,並且還專門有一個itertools標準庫用來做迭代器物件的相關處理。

迭代器魔法沒有裝飾器那麼驚豔,卻有著魔幻的力量,配合生成器,更有另一番天地。

相關文章