Python 擴充之迭代器

Rocky0429發表於2018-12-21

寫在之前

今天來講講「迭代器」的內容,其實已經拖了好多天了,感覺再不寫就要忘記了。「迭代」相信對你來說已經不陌生了,我前面曾經專門用一篇文章來講,如果你已經沒有什麼印象的話,就再點進去看看(零基礎學習 Python 之初識迭代)。

迭代器

首先我們先來看一種檢查是否可迭代的方法:

>>> hasattr(list,`__iter__`)
True

可以用上面的這種方法檢查已經學習過的其他預設型別的物件,比如字串,列表,字典等是否是可迭代的。

iter() 是一個特殊方法,它是迭代規則的基礎,有了它,就說明物件是可迭代的。跟迭代有關的一個內建函式 iter(),這個函式我們在之前的文章中介紹過,它返回的是一個迭代器物件,比如像下面這樣:

>>> list1 = [1,2,3,4]
>>> iter_list = iter(list1)
>>> iter_list
<list_iterator object at 0x00000000021CE438>

從上述程式碼的結果可以看出,iter_list 引用的是迭代器物件。那麼在這裡有一個問題,iter_list 和 list1 有區別嗎?我們來試一下:

>>> hasattr(list1,`__iter__`)
True
>>> hasattr(iter_list,`__iter__`)
True

從上面看出它們都有 iter,說明它們都是可迭代的。

>>> hasattr(list1,"__next__")
False
>>> hasattr(iter_list,"__next__")
True

我們把像 iter_list 所引用的物件那樣,稱之為「迭代器物件」。顯而易見的是,迭代器物件必然是可迭代的,反正則不一定。且 Python 中迭代器物件實現的是 next() 方法。

為了體現一下 Python 在這的強大之處,我們先來寫一個迭代器物件:

class MyRange:
   def __init__(self,n):
       self.i = 1
       self.n = n

   def __iter__(self):
       return self

   def __next__(self):
       if self.i <= self.n:
           i = self.i
           self.i += 1
           return i
       else:
           raise StopIteration()

if __name__ == "__main__":
   x = MyRange(5)
   print([i for i in x])

上述程式碼的執行結果如下所示:

[1,2,3,4,5]

上述的程式碼仿寫了類似 range() 的類,但是與 range() 又有所不同,除了結果不同以外還包括以下 2 點:

1.__iter__() 是類中的核心,它返回了迭代器的本身,一個實現了 iter() 方法的物件,就意味著它是可迭代的。

2.實現了 next() 方法,從而使得這個物件是迭代器物件。

接下來我們來看看 range() 本身:

>>> a = range(5)
>>> hasattr(a,`__iter__`)
True
>>> hasattr(a,`__next__`)
False
>>> print(a)
range(0, 5)

由上面我們就可以看出,其實我們所寫的類和 range() 本身還是有很大區別的。

通過上面的內容和我們之前的文章對迭代的講述,下面我們對迭代器做一個概括:

1.在 Python 中,迭代器是遵循迭代協議的物件。我們可以使用 iter() 從任何序列得到迭代器(exp: list,turple,set and so on)。

2.當自己編寫迭代器的類的時候,其中實現 iter() 和 next() 方法,如果沒有元素的話,會引發 StopIteration 異常。

3.如果有很多值的話,列表會佔用太多的記憶體,而迭代器則佔用的更少,它從第一個元素開始訪問,直到所有的元素被訪問完結束,只能向前衝,不能後退。

迭代器不僅僅是實用而已,而且也非常的有趣,讓我們來看下面的操作:

>>> list1 = [x**x for x in range(3)]
>>> list1
[1, 1, 4]
>>> for i in list1:print(i)
...
1
1
4
>>> for i in list1:print(i)
...
1
1
4

我們在上面重複兩次呼叫列表 list1 進行迴圈,都是能正常進行的,這個列表相當於一個可以長久使用的東西,可以重複使用。

在 Python 中,除了列表解析式以外,還可以做成元組解析式,方法也是非常的簡單:

>>> tuple1 = (x**x for x in range(3))
>>> tuple1
<generator object <genexpr> at 0x0000000001DF16D8>
>>> for i in tuple1:print(i)
...
1
1
4
>>> for i in tuple1:print(i)
...

對於 tuple1,我們可以看到它是一個 generator 物件,關於這個是啥我們先不管,後面我會單獨來說的。當我們把它用到迴圈中的時候,它明顯是個一次性用品,再次使用的時候它就什麼也不顯示了。

>>> type(list1)
<class `list`>
>>> type(tuple1)
<class `generator`>

由上面可以看出,list1 和 tuple1 是兩種不同的物件,它們之間的區別不僅僅是 tuple1 是一個元組這麼簡單,它還是 generator。其它的我們先不管,你可以嘗試一下在互動模式下輸入 dir(tuple1),檢視它是否有 iternext,我可以先告訴你,是有的。

既然是有的,那麼 tuple1 引用的就是一個迭代器的物件,它的 next() 方法促使它只能向前。

寫在之後

迭代器到這就寫完了,從內容來看迭代器確實有其過人之處,但是它不是萬能的,比如它只能向前,不能回退。還有一個是迭代器並不適合在多執行緒的環境中對可變集合使用,現在這個東西看起來可能還是有點困難,如果以後有機會寫多執行緒的話,再做解釋。

更多內容,歡迎關注公眾號「Python空間」,期待和你的交流。

相關文章