1. 程式人生 > >Python進階:設計模式之迭代器模式

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+本精選電子書。