课程

在本教程中,我们将讨论 Python 中的 enumerate() 函数是什么、何时使用、它创建什么样的对象、其语法以及工作原理。随后,我们将通过示例枚举 Python 中不同类型的对象,比较 enumerate() 函数的替代方案,并说明为什么它们不如 enumerate() 适合完成同一任务。
什么是 Python 中的 Enumerate,它何时使用?
enumerate() 是一个 Python 内置函数,它接受一个可迭代对象(项目集合或任何支持迭代的 Python 对象,如元组、列表、集合或字符串),在底层遍历其项目并返回一个 enumerate 对象。换句话说,该函数为可迭代对象的每个项目分配一个从 1 递增的计数,帮助我们在对该对象进行循环时跟踪迭代。创建 enumerate 对象后,我们可以将其转换为列表、元组或字典(分别使用 list()、tuple() 或 dict() 函数),或者直接遍历该对象以同时访问项目及其对应的索引。
与使用 for 循环相比,使用 enumerate() 函数在内存和计算效率方面更有优势,因为它一次就返回索引及其对应的项目。
语法
enumerate() 函数的语法非常简单:
enumerate(iterable, start=0)
其中,iterable 是我们希望枚举的可迭代对象,start 是一个可选参数,用于定义计数器的起始索引(默认情况下为典型的 Python 从 0 开始的索引)。
请注意,对可迭代对象应用 enumerate() 函数并不会改变原始对象本身。相反,如果我们希望后续还能使用结果,则应将生成的 enumerate 对象赋值给一个变量。
现在,让我们看看该函数在不同类型的 Python 可迭代对象上的工作方式。
枚举列表(List)
首先,我们将对一个 Python 列表应用 enumerate() 函数:
drinks = ['tea', 'coffee', 'cappuccino', 'lemonade']
enumerated_drinks = enumerate(drinks)
print(type(enumerated_drinks))
print(enumerated_drinks)
输出:
<class 'enumerate'>
<enumerate object at 0x000002DC3A1145C0>
结果对象的类型是 enumerate,而当我们尝试打印它时,只是再次确认了对象类型以及它在计算机内存中的存放位置。让我们尝试像对 Python 列表或元组那样访问该对象的第一个元素:
print(enumerated_drinks[0])
输出:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5948/1233607596.py in <module>
----> 1 print(enumerated_drinks[0])
TypeError: 'enumerate' object is not subscriptable
我们得到了一个错误,表示无法以这种方式访问 enumerate 对象的组成部分。为此,我们需要将其转换为列表、元组或字典,或者直接遍历 enumerate 对象。让我们把 enumerate 对象转换为列表:
list_enumerated_drinks = list(enumerated_drinks)
print(list_enumerated_drinks)
输出:
[(0, 'tea'), (1, 'coffee'), (2, 'cappuccino'), (3, 'lemonade')]
现在我们清楚地看到了结果列表的内部结构:每个项目都是一个包含两个元素的元组,第一个元素是原始列表中对应项目的索引,第二个元素是项目本身。我们可以使用常见的索引方式访问该列表的元素:
print(list_enumerated_drinks[0])
print(list_enumerated_drinks[0][1])
输出:
(0, 'tea')
tea
现在我们来把从原始列表创建的 enumerate 对象转换为元组。这一次,我们还将可选参数 start 设置为 1:
tuple_enumerated_drinks = tuple(enumerate(drinks, start=1))
print(tuple_enumerated_drinks)
输出:
((1, 'tea'), (2, 'coffee'), (3, 'cappuccino'), (4, 'lemonade'))
上述元组的内部结构与之前创建的列表相同。不过这一次,我们把计数改成了更“贴近人类”的方式,而不是 Python 的从 0 开始的索引。
这里有一个值得一提的与 enumerate 对象相关的方法:next()。让我们重新创建 enumerated_drinks 对象,并试试这个方法:
enumerated_drinks = enumerate(drinks)
print(next(enumerated_drinks))
print(next(enumerated_drinks))
print(next(enumerated_drinks))
print(next(enumerated_drinks))
print(next(enumerated_drinks))
输出:
(0, 'tea')
(1, 'coffee')
(2, 'cappuccino')
(3, 'lemonade')
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_5948/330134057.py in <module>
4 print(next(enumerated_drinks))
5 print(next(enumerated_drinks))
----> 6 print(next(enumerated_drinks))
StopIteration:
可以看到,每次在 enumerate 对象上调用该方法,都会返回其中的下一个项目(也再次验证了每个项目都是包含计数和值的 2 元组)。当所有项目都被取完后,enumerate 对象会被耗尽,再次调用 next() 方法会抛出 StopIteration 错误。确实,如果我们现在尝试把该 enumerate 对象转换为列表,会发现它是空的:
print(list(enumerated_drinks))
输出:
[]
这意味着每次对 enumerate 对象应用 next() 方法,都会从对象中取走一个项目,因此它会一次次变小,直到完全耗尽。稍后在讨论对其应用 for 循环时,我们还会看到 enumerate 对象的这一特性。
如果您想了解更多关于 Python 列表和其他 Python 基础概念的内容,可以学习 DataCamp 的 Introduction to Python 课程。
枚举元组(Tuple)
对元组应用 enumerate() 函数与对列表的用法相同:
drinks = ('tea', 'coffee', 'cappuccino', 'lemonade')
enumerated_drinks = enumerate(drinks)
print(enumerated_drinks)
输出:
<enumerate object at 0x000002DC3A098D40>
同样,我们得到了一个 enumerate 对象,其项目不能通过常规的 Python 索引访问。让我们把该对象转换为另一种对象,这次是字典:
dict_enumerated_drinks = dict(enumerated_drinks)
print(dict_enumerated_drinks)
输出:
{0: 'tea', 1: 'coffee', 2: 'cappuccino', 3: 'lemonade'}
结果字典中的每个键:值对都表示对应的计数-项目配对。我们可以用常见的 Python 方式访问字典的值:
print(dict_enumerated_drinks[0])
print(dict_enumerated_drinks[3])
输出:
tea
lemonade
如果您想更深入地学习 Python 字典和 pandas,请查看 Intermediate Python 课程。
枚举字符串(String)
由于 Python 字符串是字符序列,因此也是可迭代对象,我们同样可以为字符串创建一个 enumerate 对象:
enumerated_hello_world = enumerate('Hello, World!', start=10)
print(enumerated_hello_world)
输出:
<enumerate object at 0x000002DC3A145C40>
与之前一样,让我们把该对象转换为列表:
list_enumerated_hello_world = list(enumerated_hello_world)
print(list_enumerated_hello_world)
输出:
[(10, 'H'), (11, 'e'), (12, 'l'), (13, 'l'), (14, 'o'), (15, ','), (16, ' '), (17, 'W'), (18, 'o'), (19, 'r'), (20, 'l'), (21, 'd'), (22, '!')]
我们看到熟悉的 2 元组:第一个元素是字符串对应字符的计数(此处从 10 开始),第二个元素是字符本身。空格和标点符号也会被计数。
让我们重新创建 enumerated_hello_world 对象,这次不设置 start=10,并在其上应用 next 方法:
enumerated_hello_world = enumerate('Hello, World!')
print(next(enumerated_hello_world))
print(next(enumerated_hello_world))
print(next(enumerated_hello_world))
print(next(enumerated_hello_world))
print(next(enumerated_hello_world))
print(next(enumerated_hello_world))
# Creating a list from the remaining enumerate object
print(list(enumerated_hello_world))
输出:
(0, 'H')
(1, 'e')
(2, 'l')
(3, 'l')
(4, 'o')
(5, ',')
[(6, ' '), (7, 'W'), (8, 'o'), (9, 'r'), (10, 'l'), (11, 'd'), (12, '!')]
在上述代码的最后一行,我们停止调用 next() 方法,并将 enumerated_hello_world 对象转换为列表。结果列表包含了在多次调用 next() 方法后,初始 enumerate 对象剩余的字符。
结合 For 循环进行枚举
最后,让我们看看 enumerate() 函数与 for 循环结合时的工作方式:
for item in enumerate(['tea', 'coffee', 'cappuccino', 'lemonade']):
print(item)
输出:
(0, 'tea')
(1, 'coffee')
(2, 'cappuccino')
(3, 'lemonade')
每次迭代都会返回相应的计数-项目元组,就像使用 next() 方法时一样。尽管上述代码片段中不明显,但 enumerate 对象在每次迭代时也会逐个失去其项目(即这些 2 元组)。为演示这一点,我们将以不同形式重写上面的代码,并在完成所有迭代后检查结果列表的长度:
drinks = ['tea', 'coffee', 'cappuccino', 'lemonade']
enumerated_drinks = enumerate(drinks)
for item in enumerated_drinks:
print(item)
print(list(enumerated_drinks))
输出:
(0, 'tea')
(1, 'coffee')
(2, 'cappuccino')
(3, 'lemonade')
[]
与应用 next() 方法一样,遍历 enumerate 对象会逐渐将其耗尽。
不过,让我们回到前一个代码片段。如何分别访问每个 2 元组中的各个元素?为此,我们将添加一个迭代变量,对元组进行解包:
for count, drink in enumerate(['tea', 'coffee', 'cappuccino', 'lemonade']):
print(count, drink)
输出:
0 tea
1 coffee
2 cappuccino
3 lemonade
以上,我们为每个元组返回了两个变量:一个用于项目的计数,一个用于项目本身。
请注意,上述操作也可以在不使用 enumerate() 函数的情况下实现。例如,我们可以在循环外部引入一个计数变量,并在每次迭代时将其加 1:
count = 0
for drink in ['tea', 'coffee', 'cappuccino', 'lemonade']:
print(count, drink)
count += 1
输出:
0 tea
1 coffee
2 cappuccino
3 lemonade
或者,我们可以在预定义的列表(或元组、字符串等)上组合使用 range() 和 len() 函数:
drinks = ['tea', 'coffee', 'cappuccino', 'lemonade']
for count in range(len(drinks)):
print(count, drinks[count])
输出:
0 tea
1 coffee
2 cappuccino
3 lemonade
此外,在这两种 enumerate() 的替代方案中,我们仍然可以将计数变量调整为非零起始值:
count = 1
for drink in ['tea', 'coffee', 'cappuccino', 'lemonade']:
print(count, drink)
count += 1
print('\n')
drinks = ['tea', 'coffee', 'cappuccino', 'lemonade']
for count in range(len(drinks)):
print(count + 1, drinks[count])
输出:
1 tea
2 coffee
3 cappuccino
4 lemonade
1 tea
2 coffee
3 cappuccino
4 lemonade
然而,与使用 enumerate() 函数相比,上述两种方法都更笨拙、容易出错(例如,我们可能会忘记在每次迭代时手动更新计数器),并且需要更多代码。因此,每当我们需要在每次迭代中同时访问可迭代对象的计数和值时,使用 enumerate() 函数都是最符合 Python 风格的做法。
总结
在本教程中,我们学习了关于 Python 中 enumerate() 函数的诸多内容,包括其多种用法、可能的替代方案,以及为什么它最终比替代方案更高效。对于大多数 Python 初学者来说,enumerate() 函数可能并不熟悉,甚至一些高级程序员也可能不了解这个非常实用的函数。
如果您想进一步了解 enumerate(),可以考虑学习 Python Data Science Toolbox (Part 2) 课程,该课程深入探讨迭代器和可迭代对象,并提供交互式练习帮助您实践新技能。此外,如果您希望掌握其他 Python 概念,您可能会对 Learn Python with DataCamp 感兴趣,其中包含多种技能与职业路径、速查表、教程、Python 认证和测评。