天天看點

第19天:Python 之疊代器

by 軒轅禦龍

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

通過簡單地使用<code>for</code>和<code>in</code>兩個關鍵字,我們可以很輕松地實作在 C 語言中繁瑣的周遊操作。相比較而言,C 語言中要實作相同的功能,需要這樣寫(假設存在整型數組<code>list_example</code>):

顯而易見,在周遊元素的操作上,Python 的表達更加直覺優雅,簡潔明了;這正是因為 Python 在實作<code>for</code>語句的時候,恰到好處地使用了“疊代器”的概念。

疊代器在 Python 中随處可見,并且具有統一的标準。通過使用疊代器,Python 能夠逐個通路清單<code>list_example</code>中的每個元素。

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

疊代器(iterator)是一種可在容器(container)中遍訪的接口,為使用者封裝了内部邏輯。 ——百度百科·疊代器 大意

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

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

對于疊代器對象本身來說,需要具有<code>__iter__()</code>和<code>__next__()</code>兩種方法,二者合稱為“疊代器協定”。也就是說,隻要同時具有這兩種方法,Python 解釋器就會認為該對象是一個疊代器;反之,隻具有其中一個方法或者二者都不具有,解釋器則認為該對象不是一個疊代器。

上述論斷可由下面的代碼驗證(需要用到内置函數<code>isinstance()</code>,來判斷一個對象是否是某個類的執行個體;該用法啟發于[廖雪峰的官方網站]):

由第 8~11 行的代碼可知,對于 Python 來說,判斷一個對象是否是疊代器的标準僅僅是“是否同時具有<code>__iter__()</code>和<code>__next__()</code>這兩個方法”。

并且從第 17~20 行的代碼也可以驗證上述推斷:隻具有方法<code>__next__()</code>既不是可疊代的,也不是一個疊代器。

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

疊代器對象本質上代表的是一個資料流,通過反複調用其方法<code>__next__()</code>或将其作為參數傳入<code>next()</code>函數,即可按順序逐個傳回資料流中的每一項;直到流中不再有資料項,進而抛出一個<code>StopIteration</code>異常,終止疊代。

在 Python 中内置了兩個函數:<code>iter()</code>和<code>next()</code>,分别用于“将參數對象轉換為疊代器對象”和“從疊代器中取出下一項”。

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

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

是以直接調用對象的<code>__iter__()</code>和<code>__next__()</code>方法與将對象作為參數傳入内置函數<code>iter()</code>和<code>next()</code>是等效的。

要注意的一點在于,對疊代器調用其本身的<code>__iter__()</code>方法,得到的将會是這個疊代器自身,該疊代器相關的狀态都會被保留,包括該疊代器目前的疊代狀态。見下述代碼:

顯然,清單<code>li</code>本身并不是一個疊代器,而将其傳入内置函數<code>iter()</code>就得到了相應于清單<code>li</code>的疊代器<code>li_iterator</code>。我們調用<code>next()</code>函數來疊代它:

一切都在預料之中。我們再來将其本身作為參數傳入内置函數<code>iter()</code>:

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

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

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

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

有了上面的讨論,我們就可以自己實作一個簡單的疊代器。隻要確定這個簡單疊代器具有與疊代器定義相符的行為即可。

說人話就是:要定義一個資料類型,具有<code>__iter__()</code>方法并且該方法傳回一個帶有<code>__next__()</code>方法的對象,而當該類已經具有<code>__next__()</code>方法時則傳回其本身。示例代碼如下:

驗證一下:

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

任務完成!

回到文章開頭我們作為引子的<code>for</code>循環示例,實際上在執行<code>for</code>語句的時候,Python 悄悄調用了内置函數<code>iter()</code>,并将<code>for</code>語句中的容器對象作為參數傳入;而函數<code>iter()</code>傳回值則是一個疊代器對象。

是以,<code>for</code>語句是将容器對象轉換為疊代器對象之後,調用<code>__next__()</code>方法,逐個通路原容器中的各個對象,直到周遊完所有元素,抛出一個<code>StopIteration</code>異常,并終止<code>for</code>循環。

疊代器(iterator)首先要是可疊代的(iterable);即疊代器一定是可疊代的,但可疊代的不一定是疊代器

可疊代的對象意味着可以被轉換為疊代器

疊代器需要同時具有方法<code>__iter__()</code>和<code>__next__()</code>

對疊代器調用<code>iter()</code>函數,得到的是這個疊代器本身

<code>for</code>循環實際上使用了疊代器,并且一般情況下将異常<code>StopIteration</code>作為循環終止條件

本文探究了 Python 中疊代器的相關知識點,深入了解了疊代器的屬性和行為,學到了兩個重要的方法<code>__iter__()</code>和<code>__next__()</code>。同時搞明白了 Python 實作<code>for</code>循環的内部機制。

示例代碼:Python-100-days-day019

[1] Python3 文檔-内置類型

[2] 廖雪峰的官方網站

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

關注公衆号:python技術,回複"python"一起學習交流

第19天:Python 之疊代器

作者:純潔的微笑

出處:www.ityouknow.com

資源:微信搜【純潔的微笑】關注我,回複 【程式員】【面試】【架構師】有我準備的一線程式必備計算機書籍、大廠面試資料和免費電子書。 一共1024G的資料,希望可以幫助大家提升技術和能力。

本文如對您有幫助,還請多幫 【推薦】 下此文。

點我了解:Tooool-程式員一站式導航網站