在軟件開發領域中,人們常常會用到這一個概念——「設計模式」(design pattern),它是一種針對軟件設計的共性問題而提出的解決方案。在一本聖經級的書籍《設計模式:可複用面向對象軟件的基礎》(1991年,Design Patterns - Elements of Reusable Object-Oriented Software)中,它提出了23種設計模式。迭代器模式就是其中的一種,在各類編程語言中都獲得了普遍的應用。java
本文將談談 Python 中的迭代器模式,主要內容:什麼是迭代器模式、Python 如何實現迭代器模式、itertools 模塊建立迭代器的方法、其它運用迭代器的場景等等,期待與你共同窗習進步。python
維基百科有以下定義:算法
迭代器是一種最簡單也最多見的設計模式。它可讓用戶透過特定的接口巡訪容器中的每個元素而不用瞭解底層的實現。——維基百科編程
簡單地說,迭代器模式就是一種通用性的能夠遍歷容器類型(如序列類型、集合類型等)的實現方式。使用迭代器模式,能夠不關心遍歷的對象具體是什麼(如字符串、列表、字典等等),也不須要關心遍歷的實現算法是什麼,它關心的是從容器中遍歷/取出元素的結果。設計模式
按遍歷方式劃分,迭代器可分爲內部迭代器與外部迭代器,它們的區別在於執行迭代動做與維持迭代狀態的不一樣。編程語言
一般而言,迭代器是一次性的,當迭代過一輪後,再次迭代將獲取不到元素。函數
因爲迭代器模式的使用太常見了,因此大多數編程語言都給常見的容器類型實現了它,例如 Java 中的 Collection,List、Set、Map等。在 Java 中使用迭代器遍歷 List 能夠這麼寫:學習
List<String> list = new ArrayList<>(); Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); }
ArrayList 類經過自身的 iterator() 方法得到一個迭代器 iterator,而後由該迭代器實例來落實遍歷過程。設計
Python 固然也應用了迭代器模式,但它的實現思路跟上例卻不太同樣。code
首先,Python 認爲遍歷容器類型並不必定要用到迭代器,所以設計了可迭代對象。
list = [1,2,3,4] for i in list: print(i,end=" ") # 1 2 3 4 for i in list: print(i,end=" ") # 1 2 3 4
上例中的 list 是可迭代對象(Iterable),但並非迭代器(雖然在底層實現時用了迭代器的部分思想)。Python 抓住了迭代器模式的本質,便是「迭代」,賦予了它極高的地位。
如此設計的好處顯而易見:(1)寫法簡便,用意直白;(2)可重複迭代,避免一次性迭代器的缺陷;(3)不須要建立迭代器,減小開銷。
可迭代對象可看做是廣義的迭代器,同時,Python 也設計了普通意義的狹義的迭代器。
list = [1,2,3,4] it = iter(list) for i in it: print(i,end=" ") # 1 2 3 4 for i in it: print(i,end=" ") # 無輸出
上例中的 iter() 方法會將可迭代對象變成一個迭代器。從輸出結果能夠看出,該迭代器的迭代過程是一次性的。
由此看來,Python 實際上是將「迭代器模式」一拆爲二來實現:一是可迭代思想,普遍播種於容器類型的對象中,使它們均可迭代;一是迭代器,一種特殊的可迭代對象,承擔普通意義上的迭代器所特有的迭代任務。
同時,它還提供了將可迭代對象轉化爲迭代器的簡易方法,如此安排,真是將迭代器模式的效力發揮到了極致。(關於可迭代對象與迭代器的更多區別、以及它們的實現原理,請參見《Python進階:迭代器與迭代器切片》)
建立迭代器有以下方式:(1)iter() 方法,將可迭代對象轉化成迭代器;(2)__iter__()
與 __next__()
魔術方法,定義類實現這兩個魔術方法;(3)itertools 模塊,使用內置模塊生成迭代器;(4)其它建立方法,如 zip() 、map() 、enumerate() 等等。
四類方法各有適用場所,本節重點介紹 itertools 模塊。它能夠建立三類迭代器:無限迭代器、有限迭代器與組合迭代器。
count(start=0, step=1)
:建立一個從 start (默認值爲 0) 開始,以 step (默認值爲 1) 爲步長的的無限整數迭代器。
cycle(iterable)
:對可迭代對象的元素反覆執行循環。
repeat(object [,times])
:反覆生成 object 至無限,或者到給定的 times 次。
import itertools co = itertools.count() cy = itertools.cycle('ABC') re = itertools.repeat('A', 30) # 注意:請分別執行;如下寫法未加終止判斷,只能按 Ctrl+C 退出 for n in co: print(n,end=" ") # 0 1 2 3 4...... for n in cy: print(n,end=" ") # A B C A B C A B...... for n in re: print(n,end=" ") # A A A A A A A A....(30個)
以上方法,比較經常使用的有:chain() 將多個可迭代對象(能夠是不一樣類型)鏈接成一個大迭代器;compress() 方法根據真假過濾器篩選元素;groupby() 把迭代器中相鄰的重複元素挑出來放在一塊兒;islice() 方法返回迭代器切片(用法參見《Python進階:迭代器與迭代器切片》);tee() 方法根據可迭代對象建立 n 個(默認2個)迭代器副本。
for c in itertools.chain('ABC', [1,2,3]): print(c,end=" ") # 輸出結果:A B C 1 2 3 for c in itertools.compress('ABCDEF', [1, 1, 0, 1, 0, 1]): print(c,end=" ") # 輸出結果:A B D F for key, group in itertools.groupby('aaabbbaaccd'): print(key, ':', list(group)) # 輸出結果: a : ['a', 'a', 'a'] b : ['b', 'b', 'b'] a : ['a', 'a'] c : ['c', 'c'] d : ['d'] itertools.tee('abc', 3) # 輸出結果:(<itertools._tee at 0x1fc72c08108>, <itertools._tee at 0x1fc73f91d08>, <itertools._tee at 0x1fc73efc248>)
product() :求解多個可迭代對象的笛卡爾積。
permutations() :求解可迭代對象的元素的全排列。
combinations():求解可迭代對象的元素的組合。
for i in itertools.product('ABC', [1,2]): print(i, end=" ") # 輸出結果:('A', 1) ('A', 2) ('B', 1) ('B', 2) ('C', 1) ('C', 2) for i in itertools.permutations('ABC', 2): print(i, end=" ") # 輸出結果:('A', 'B') ('A', 'C') ('B', 'A') ('B', 'C') ('C', 'A') ('C', 'B') for i in itertools.combinations('ABC', 2): print(i, end=" ") # 輸出結果:('A', 'B') ('A', 'C') ('B', 'C') for i in itertools.combinations('ABCD', 3): print(i, end=" ") # 輸出結果:('A', 'B', 'C') ('A', 'B', 'D') ('A', 'C', 'D') ('B', 'C', 'D')
迭代器模式的使用場景實在太廣泛了,而 Python 也爲迭代器的順利使用而提供了不少便利的條件,本節將介紹相關的幾個內置方法。這些方法很是經常使用並且強大,是 Python 進階的必會內容。
zip() 方法能夠同時迭代多個序列,並各取一個元素,生成一個可返回元組的迭代器。此迭代器的長度以較短序列的長度保持一致,若想生成較長序列的長度,須要使用 itertools 模塊的 zip_longest() 方法。
import itertools a = [1, 2, 3] b = ['w', 'x', 'y', 'z'] for i in zip(a,b): print(i,end=" ") # (1, 'w') (2, 'x') (3, 'y') # 空缺值以 None 填補 for i in itertools.zip_longest(a,b): print(i,end=" ") # (1, 'w') (2, 'x') (3, 'y') (None, 'z')
enumerate() 方法接收一個序列類型參數,生成一個可返回元組的迭代器,元組內容是下標及其對應的元素值。它還可接收一個可選參數,指定下標的起始值,默認是0 。
注意:衆所周知,Python 中序列的索引值從 0 開始,可是,enumerate() 能夠達到改變起始索引數值的效果。
seasons = ['Spring', 'Summer', 'Fall', 'Winter'] for i in enumerate(seasons): print(i,end=" ") #輸出結果:(0, 'Spring') (1, 'Summer') (2, 'Fall') (3, 'Winter') for i in enumerate(seasons, start=7): print(i,end=" ") #輸出結果:(7, 'Spring') (8, 'Summer') (9, 'Fall') (10, 'Winter')
map() 方法的參數是一個函數及一個或多個可迭代對象,它會將可迭代對象的元素映射到該函數中,而後迭代地運行該函數,返回結果也是一個迭代器。當存在多個可迭代對象參數時,迭代長度等於較短對象的長度。
def square(x): return x ** 2 l = map(square, [1, 2, 3, 4, 5]) print(list(l)) # 輸出結果:[1, 4, 9, 16, 25] m = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10, 2]) print(list(m)) # 輸出結果:[3, 7, 11, 15, 19]
filter() 方法的參數是一個判斷函數及一個可迭代對象,遍歷可迭代對象執行判斷函數,過濾下判斷爲True 的元素,與它相對,若想保留判斷爲 False 的元素,可以使用 itertoole 模塊的 filterfalse() 方法。
import itertools fi = filter(lambda x: x%2, range(10)) ff = itertools.filterfalse(lambda x: x%2, range(10)) for i in fi: print(i,end=" ") # 輸出結果:1 3 5 7 9 for i in ff: print(i,end=" ") # 輸出結果:0 2 4 6 8
迭代器模式幾乎是 23 種設計模式中最經常使用的設計模式,本文主要介紹了 Python 是如何運用迭代器模式,並介紹了 itertools 模塊生成迭代器的 18 種方法,以及 5 種生成迭代器的內置方法。
相關連接:
itertools模塊文檔:http://t.cn/R6cGtfw
-----------------
本文原創並首發於公#衆~號【Python貓】,後臺回覆「愛學習」,免費得到20+本精選電子書。