Python 之迭代器

Python 之迭代器

1 概念引入

在之前的教程中,我們已經接觸過一些典型的for語句,比如:

>>> list_example = [0, 1, 2, 3, 4]
>>> for i in list_example:
...  print(i)
...
0
1
2
3
4

通過簡單地使用forin兩個關鍵字,我們可以很輕鬆地實現在 C 語言中繁瑣的遍歷操作。相比較而言,C 語言中要實現相同的功能,需要這樣寫(假設存在整型數組list_example):

int i;
for(i = 0; i < list_length; i++)
    printf("%d\n", list_example[i]);

顯而易見,在遍歷元素的操作上,Python 的表達更加直觀優雅,簡潔明瞭;這正是因爲 Python 在實現for語句的時候,恰到好處地使用了“迭代器”的概念。

迭代器在 Python 中隨處可見,並且具有統一的標準。通過使用迭代器,Python 能夠逐個訪問列表list_example中的每個元素。

下面我們來進一步討論相關的機制。

2 定義及原理

2.1 迭代器的定義

迭代器(iterator)是一種可在容器(container)中遍訪的接口,爲使用者封裝了內部邏輯。

——百度百科·迭代器 大意

上面是我們可以查到的、對“迭代器”的一個寬泛的定義。

而具體到 Python 中,迭代器也屬於內置的標準類之一,是與我們之前學習過的“序列”同一層次的概念。

對於迭代器對象本身來說,需要具有__iter__()__next__()兩種方法,二者合稱爲“迭代器協議”。也就是說,只要同時具有這兩種方法,Python 解釋器就會認爲該對象是一個迭代器;反之,只具有其中一個方法或者二者都不具有,解釋器則認爲該對象不是一個迭代器。

上述論斷可由下面的代碼驗證(需要用到內置函數isinstance(),來判斷一個對象是否是某個類的實例;該用法啓發於[廖雪峯的官方網站]):

>>> from collections import Iterable, Iterator, Container
>>> class bothIterAndNext:
... 	def __iter__(self):
... 		pass
... 	def __next__(self):
... 		pass
...
>>> isinstance(bothIterAndNext(), Iterable) # 兩種方法都有的對象是可迭代的
True
>>> isinstance(bothIterAndNext(), Iterator) # 兩種方法都有的對象是迭代器
True
>>> 
>>> class onlyNext:
... 	def __next__(self):
... 		pass
...
>>> isinstance(onlyNext(), Iterable) # 只有方法 __next__() 是不可迭代的
False
>>> isinstance(onlyNext(), Iterator) # 只有方法 __next__() 不是迭代器
False
>>> 
>>> class onlyIter:
... 	def __iter__(self):
... 		pass
...
>>> isinstance(onlyIter(), Iterable) # 只有方法 __iter__() 是可迭代的
True
>>> isinstance(onlyIter(), Iterator) # 只有方法 __iter__() 不是迭代器
False

由第 8~11 行的代碼可知,對於 Python 來說,判斷一個對象是否是迭代器的標準僅僅是“是否同時具有__iter__()__next__()這兩個方法”。

並且從第 17~20 行的代碼也可以驗證上述推斷:只具有方法__next__()既不是可迭代的,也不是一個迭代器。

有意思的事情發生在代碼第 26、27 兩行:代碼輸出結果顯示,只有方法__iter__()的對象居然是可迭代的!(後文解釋)

2.2 迭代器的實質

迭代器對象本質上代表的是一個數據流,通過反覆調用其方法__next__()或將其作爲參數傳入next()函數,即可按順序逐個返回數據流中的每一項;直到流中不再有數據項,從而拋出一個StopIteration異常,終止迭代。

在 Python 中內置了兩個函數:iter()iter(),分別用於“將參數對象轉換爲迭代器對象”和“從迭代器中取出下一項”。

實際上所有具有方法__iter__()的對象均被視作“可迭代的”。因爲方法__iter__()進行的操作其實就是返回一個該對象對應的迭代器,也就是說“可迭代的(iterable)”的真實含義其實是“可以被轉換爲迭代器(iterator)的”。而內置函數iter()也是調用對象本身具有的__iter__()方法來實現特定對象到迭代器的轉換。

相應地,內置函數next()其實是調用了對象本身的方法__next__(),而該方法執行的操作就是從對象對應的數據流中取出下一項。

因此直接調用對象的__iter__()__next__()方法與將對象作爲參數傳入內置函數iter()next()是等效的。

要注意的一點在於,對迭代器調用其本身的__iter__()方法,得到的將會是這個迭代器自身,該迭代器相關的狀態都會被保留,包括該迭代器目前的迭代狀態。見下述代碼:

>>> li = [1, 2, 3]
>>> li_iterator = iter(li)
>>> isinstance(li, Iterator)
False
>>> isinstance(li_iterator, Iterator)
True

顯然,列表li本身並不是一個迭代器,而將其傳入內置函數iter()就得到了相應於列表li的迭代器li_iterator。我們調用next()函數來迭代它:

>>> next(li_iterator)
1
>>> next(li_iterator)
2

一切都在預料之中。我們再來將其本身作爲參數傳入內置函數iter()

>>> li_iterator = iter(li_iterator)
>>> next(li_iterator)
3

到這裏跟我們希望的就有所出入了。在使用這樣一個語句的時候,通常我們的目的都是得到一個新的迭代器,而非跟原先的迭代器一樣的對象。

更進一步地,我們還可以發現,對迭代器調用iter()函數得到的對象不僅與原先的迭代器具有相同的狀態,它們其實就是指向同一個對象

>>> id(li_iterator)
2195581916440
>>> li_iterator = iter(li_iterator)
>>> id(li_iterator)
2195581916440
>>> li_iterator2 = iter(li_iterator)
>>> id(li_iterator2)
2195581916440

也就是說在對象本身就是一個迭代器的情況下,生成的對應迭代器的時候 Python 不會進行另外的操作,就返回這個迭代器本身作爲結果。

3 實現一個迭代器類

本節構建類的代碼來自[Python3 文檔-類-9.8 迭代器]

有了上面的討論,我們就可以自己實現一個簡單的迭代器。只要確保這個簡單迭代器具有與迭代器定義相符的行爲即可。

說人話就是:要定義一個數據類型,具有__iter__()方法並且該方法返回一個帶有__next__()方法的對象,而當該類已經具有__next__()方法時則返回其本身。示例代碼如下:

class Reverse:
    """反向遍歷序列對象的迭代器"""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

驗證一下:

>>> rev = Reverse('justdopython.com')
>>> next(rev)
'm'
>>> next(rev)
'o'
>>> next(rev)
'c'
>>> next(rev)
'.'

(o゜▽゜)o☆[BINGO!]

任務完成!

4 for語句與迭代器

回到文章開頭我們作爲引子的for循環示例,實際上在執行for語句的時候,Python 悄悄調用了內置函數iter(),並將for語句中的容器對象作爲參數傳入;而函數iter()返回值則是一個迭代器對象。

因此,for語句是將容器對象轉換爲迭代器對象之後,調用__next__()方法,逐個訪問原容器中的各個對象,直到遍歷完所有元素,拋出一個StopIteration異常,並終止for循環。

5 總結

  • 迭代器(iterator)首先要是可迭代的(iterable);即迭代器一定是可迭代的,但可迭代的不一定是迭代器
  • 可迭代的對象意味着可以被轉換爲迭代器
  • 迭代器需要同時具有方法__iter__()__next__()
  • 對迭代器調用iter()函數,得到的是這個迭代器本身
  • for循環實際上使用了迭代器,並且一般情況下將異常StopIteration作爲循環終止條件

本文探究了 Python 中迭代器的相關知識點,深入理解了迭代器的屬性和行爲,學到了兩個重要的方法__iter__()__next__()。同時搞明白了 Python 實現for循環的內部機制。

參考資料

[1] Python3 文檔-內置類型

[2] 廖雪峯的官方網站

[3] Python3 文檔-類-9.8 迭代器

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