Python 之迭代器
1 概念引入
在之前的教程中,我們已經接觸過一些典型的for
語句,比如:
>>> list_example = [0, 1, 2, 3, 4]
>>> for i in list_example:
... print(i)
...
0
1
2
3
4
通過簡單地使用for
和in
兩個關鍵字,我們可以很輕鬆地實現在 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] 廖雪峯的官方網站