【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

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