【Python】詳解 enumerate + zip

目錄

一、枚舉函數 —— enumerate

二、拉鍊函數 —— zip

三、小結


一、枚舉函數 —— 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

https://bbs.huaweicloud.com/blogs/155466

https://www.cnblogs.com/xuchunlin/p/6676304.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章