目錄
一、枚舉函數 —— enumerate
enumerate(iterable, start=0)
枚舉函數返回一個枚舉對象 —— 迭代器 iterator。每次迭代,內部將調用 __next__ 方法並返回一個 tuple,內含一個計數值 (默認從 start=0 開始計數) 和一個獲取自可迭代對象 iterable 的值。例如:
>>> x = ['a', 'b', 'c', 'd', 'e']
>>> for i in enumerate(x):
print(i)
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')
(4, 'e')
# ----------------------------------------------------------------------------
>>> list(enumerate(x)) # 迭代器 iterator 是惰性的, 常用顯示類型轉換令其強制運算
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]
但通常會用兩個局部變量來對每次迭代的 tuple 解包,以便於接收迭代內容的同時,獲取額外的計數值 (常用作索引) 而無需自定義計數器:
>>> index = -1 # 初始化計數器 (全局變量)
>>> for element in x: # 使用自定義計數器
index += 1
print(index, element)
0 a
1 b
2 c
3 d
4 e
# --------------------------------------------------------------------
>>> for (index, element) in enumerate(x): # 使用 enumerate 關鍵字
0 a
1 b
2 c
3 d
4 e
# --------------------------------------------------------------------
>>> for index, element in enumerate(x): # 對於非嵌套的可迭代對象, 兩種用法等價
print(index, element)
0 a
1 b
2 c
3 d
4 e
事實上,全局變量的查詢和訪問速度低於局部變量,應儘量減少全局變量的使用,尤其是在循環中,而使用 enumerate 關鍵字就是一個不錯的替代方案。
此外,enumerate 的參數 start 作爲計數初值,默認值爲 0。若有需要,可指定爲任意整數傳入:
>>> for index, element in enumerate(x, 3): # 指定計數初值 start=3
print(index, element)
3 a
4 b
5 c
6 d
7 e
注意,對於嵌套元素的可迭代對象 (如元組列表、嵌套列表等),只有一種解包方式:
>>> y = [('a',6), ('b',7), ('c',8), ('d',9)]
>>> for index, (elem, num) in enumerate(y):
print(index, elem, num)
0 a 6
1 b 7
2 c 8
3 d 9
# -------------------------------------------------------------------------
>>> for index, elem, num in enumerate(y):
print(index, elem, num)
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
for index, elem, num in enumerate(y):
ValueError: not enough values to unpack (expected 3, got 2)
總之,在一些需要靈活運用索引和迭代序列的場合下,enumerate 關鍵字頗具實用性。
二、拉鍊函數 —— zip
zip(*iterables)
拉鍊函數創建了一個迭代器 iterator,打包/聚合來自各個輸入可迭代對象 iterable 中的元素。具體而言,zip 返回一個元組迭代器,其中的第 i 個元組 tuple 包含來自各個輸入可迭代對象 iterable 的第 i 個元素。 例如:
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = ['a', 'b', 'c', 'd', 'e']
>>> for tup in zip(lst1, lst2): # 如同拉鍊的操作
print(tup)
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
(5, 'e')
# ----------------------------------------------------------------------------
>>> list(zip(lst1, lst2)) # 迭代器 iterator 是惰性的, 常用顯示類型轉換令其強制運算
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')]
把各個可迭代對象比作“拉鍊”,那麼 zip 就是一個“拉鍊頭”,將相同位置的元素同時綁定在一起。因此,當輸入可迭代對象中最短的一個被“耗盡”時,迭代器將終止迭代,截斷輸出。
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = ['a', 'b', 'c', 'd', 'e']
>>> lst3 = [0, 0, 0]
>>> for tup in zip(lst1, lst2, lst3): # lst 最短, 僅有 3 個元素, 故僅迭代 3 次便終止和截斷
print(tup)
(1, 'a', 0)
(2, 'b', 0)
(3, 'c', 0)
若僅輸入單個可迭代對象,zip 返回的迭代器由單元素 tuple 組成:
>>> lst1 = [1, 2, 3, 4, 5]
>>> for tup in zip(lst1):
print(tup)
(1,)
(2,)
(3,)
(4,)
(5,)
如果不希望其他可迭代對象因最短可迭代對象的耗盡而被截斷,則應使用 itertools.zip_longest 聚合元素,而空缺的位置將由 None 佔用:
>>> from itertools import zip_longest
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = ['a', 'b', 'c', 'd', 'e']
>>> lst3 = [0, 0, 0]
>>> for tup in zip_longest(lst1, lst2, lst3): # 可見沒有元素被截斷, 空缺處由 None 取代
print(tup)
(1, 'a', 0)
(2, 'b', 0)
(3, 'c', 0)
(4, 'd', None)
(5, 'e', None)
此外,形同拉鍊的來回移動操作,使用一次 zip 將“聚合”數據,而使用兩次 zip 則“解散”數據:
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = ['a', 'b', 'c', 'd', 'e']
>>> zipped = zip(lst1, lst2) # 使用一次 zip 拉鍊
>>> zipped
<zip object at 0x000001DBDDEDE748>
>>> list(zipped)
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')]
>>> lst3, lst4 = zip(*zip(lst1, lst2)) # 使用兩次 zip 解鏈
>>> lst3
(1, 2, 3, 4, 5)
>>> lst4
('a', 'b', 'c', 'd', 'e')
拉鍊函數 zip 的用途很多,典型的有:
參數組合
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = ['a', 'b', 'c', 'd', 'e']
>>> [str(elem2) + str(elem1) for elem1, elem2 in zip(lst1, lst2)]
['a1', 'b2', 'c3', 'd4', 'e5']
字典生成
>>> lst1 = [1, 2, 3, 4, 5]
>>> lst2 = ['a', 'b', 'c', 'd', 'e']
>>> dict(zip(lst2, lst1))
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
矩陣轉置
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> [[row[col] for row in matrix] for col in range(len(matrix[0]))] # 列表推導式實現
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# 將 list 視爲 tuple 解壓, 恰得“行列互換”的效果, 再通過 map 對各元素應用 list() 將 tuple 轉換爲 list
>>> list(map(list, zip(*matrix))) # map + zip 實現, 速度更快但也更晦澀
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
三、小結
枚舉函數 enumerate 和拉鍊函數 zip 其實在底層均是基於迭代器實現的,原理上並不難懂,不使用也無大礙。但學會靈活運用這些內置的工具和方法,將十分有助於我們踐行 Pythonic 的精髓。
參考文獻:
《Python Immediate》
https://docs.python.org/zh-cn/3.6/library/functions.html?highlight=enumerate#enumerate
https://docs.python.org/zh-cn/3.6/library/functions.html?highlight=enumerate#zip