天天看點

Python進階:設計模式之疊代器模式

在軟體開發領域中,人們經常會用到這一個概念——“設計模式”(design pattern),它是一種針對軟體設計的共性問題而提出的解決方案。在一本聖經級的書籍《設計模式:可複用面向對象軟體的基礎》(1991年,Design Patterns - Elements of Reusable Object-Oriented Software)中,它提出了23種設計模式。疊代器模式就是其中的一種,在各種程式設計語言中都得到了廣泛的應用。

本文将談談 Python 中的疊代器模式,主要内容:什麼是疊代器模式、Python 如何實作疊代器模式、itertools 子產品建立疊代器的方法、其它運用疊代器的場景等等,期待與你共同學習進步。

1、什麼是疊代器模式?

維基百科有如下定義:

疊代器是一種最簡單也最常見的設計模式。它可以讓使用者透過特定的接口巡訪容器中的每一個元素而不用了解底層的實作。——維基百科

簡單地說,疊代器模式就是一種通用性的可以周遊容器類型(如序列類型、集合類型等)的實作方式。使用疊代器模式,可以不關心周遊的對象具體是什麼(如字元串、清單、字典等等),也不需要關心周遊的實作算法是什麼,它關心的是從容器中周遊/取出元素的結果。

按周遊方式劃分,疊代器可分為内部疊代器與外部疊代器,它們的差別在于執行疊代動作與維持疊代狀态的不同。

通常而言,疊代器是一次性的,當疊代過一輪後,再次疊代将擷取不到元素。

2、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 當然也應用了疊代器模式,但它的實作思路跟上例卻不太一樣。

首先,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進階:疊代器與疊代器切片》)

3、建立疊代器

建立疊代器有如下方式:(1)iter() 方法,将可疊代對象轉化成疊代器;(2)

__iter__()

__next__()

魔術方法,定義類實作這兩個魔術方法;(3)itertools 子產品,使用内置子產品生成疊代器;(4)其它建立方法,如 zip() 、map() 、enumerate() 等等。

四類方法各有适用場所,本節重點介紹 itertools 子產品。它可以建立三類疊代器:無限疊代器、有限疊代器與組合疊代器。

3.1 無限疊代器

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個)
           

3.2 有限疊代器

以上方法,比較常用的有: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>)
           

3.3 組合疊代器

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')
           

4、強大的内置疊代器方法

疊代器模式的使用場景實在太普遍了,而 Python 也為疊代器的順利使用而提供了很多便利的條件,本節将介紹相關的幾個内置方法。這些方法非常常用而且強大,是 Python 進階的必會内容。

4.1 zip() 方法

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')
           

4.2 enumerate() 方法

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')
           

4.3 map() 方法

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]
           

4.4 filter() 方法

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
           

5. 小結

疊代器模式幾乎是 23 種設計模式中最常用的設計模式,本文主要介紹了 Python 是如何運用疊代器模式,并介紹了 itertools 子產品生成疊代器的 18 種方法,以及 5 種生成疊代器的内置方法。

相關連結:

itertools子產品文檔:http://t.cn/R6cGtfw

Python進階:疊代器與疊代器切片

Python進階:全面解讀進階特性之切片!

-----------------

本文原創并首發于微信公衆号【Python貓】,背景回複“愛學習”,免費獲得20+本精選電子書。