简介
使用计数器变量/索引的循环--这是计算机科学中的经典之作!通常情况下,你会明确定义一个计数器变量/索引,并在每个循环中手动增加它,或者你会使用某种语法糖,通过增强的for 循环来避免这一过程。
some_list = ['Looping', 'with', 'counters', 'is', 'a', 'classic!']
# Manual counter incrementation
i = 0
for element in some_list:
print(f'Element Index: {i}, Element: {element}')
i += 1
# Automatic counter incrementation
for i in range(len(some_list)):
print(f'Element Index: {i}, Element: {some_list[i]}')
这两个片段的输出结果都是一样的。
Element Index: 0, Element: Looping
Element Index: 1, Element: with
Element Index: 2, Element: counters
Element Index: 3, Element: is
Element Index: 4, Element: a
Element Index: 5, Element: classic!
由于像这样的循环在日常工作中非常普遍--enumerate() 函数被内置到Python命名空间中。你可以,没有任何额外的依赖,在Python中通过一个迭代器进行循环,有一个自动的计数器变量/索引,语法简单如。
for idx, element in enumerate(some_list):
print(idx, element)
**注意:**如果没有其他标签,将索引命名为idx 是常见的,但不是必须的,因为id 是一个保留关键字。通常,根据你所处理的迭代器,可以赋予更有意义的名字,例如。batch_num, batch in enumerate(...).
这段代码的结果是。
0 Looping
1 with
2 counters
3 is
4 a
5 classic!
让我们深入了解这个函数,并探索它是如何工作的!这是一个经典而常见的函数--以真正的 Python 方式,它简化了一个常见的、多余的操作,提高了代码的可读性。
Python 中的*enumerate()*函数
enumerate() 函数接受一个 可迭代的集合(如一个元组、列表或字符串),并返回一个enumerate 对象,它由一个 键集和 值集,其中键对应于一个计数器变量(从0开始),值对应于可迭代集合的原始元素。
obj = enumerate(some_list)
print(type(obj))
# <class 'enumerate'>
注意: enumerate 对象本身就是可迭代的!你可以使用标准的for 语法,对enumerate 对象的键和值进行解包。
使用Python的标准for 语法,我们可以从这个对象中解包键和值,并检查它们的类型。
for key, value in obj:
print(type(key), type(value))
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
# <class 'int'> <class 'str'>
值的数据类型(来自原始集合的元素)被保留下来,所以即使你传递了自定义的数据类型,只要它们是一个有效的可迭代的集合--它们将被简单地注释为一个计数器变量。如果你把对象本身收集成一个列表,它的结构会变得非常清楚。
print(list(obj))
# [(0, 'Looping'), (1, 'with'), (2, 'counters'), (3, 'is'), (4, 'a'), (5, 'classic!')]
它只是一组图元,每个图元有两个元素--一个计数器变量,从0开始,以及原始可迭代的每个元素映射到索引上。
你可以设置一个可选的start 参数,不是表示迭代器中的起始索引,而是表示函数将产生的第一个计数器/索引的起始值。例如,假设我们想从1 ,而不是0 。
obj = enumerate(some_list, 1)
print(list(obj))
# [(1, 'Looping'), (2, 'with'), (3, 'counters'), (4, 'is'), (5, 'a'), (6, 'classic!')]
用*enumerate()*循环遍历迭代器
说了这么多--在一个enumerate 对象中循环看起来和在其他迭代器中循环是一样的。for 循环在这里很方便,因为你可以给返回的元组值分配参考变量。此外,没有必要明确地引用该对象,因为它很少在单个循环之外使用,所以返回的值通常直接用于循环本身。
# No need to assign the returned `enumerate` object to a distinct reference variable
for idx, element in enumerate(some_list):
print(f'{idx}, {element}')
这就导致了。
0, Looping
1, with
2, counters
3, is
4, a
5, classic!
如果你想阅读更多关于f-字符串和在Python中格式化输出的信息,请阅读我们的《使用Python 3的f-字符串进行字符串格式化指南》!
注释迭代器中的每个元素--或者说,递增一个计数器并将其返回,而访问迭代器中的元素就是这么简单!
值得注意的是,在enumerate() 函数中没有什么特别的事情发生。从功能上讲,它真的等同于我们写的初始循环,有一个明确的计数器变量与一个元素一起被返回。如果你看一下官方文档中的说明,该函数的结果在功能上等同于。
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
你可以看到,这段代码与我们定义的第一个实现很相似。
# Original implementation
i = 0
for element in some_list:
print(f'Element Index: {i}, Element: {some_list[i]}')
i += 1
# Or, rewritten as a method that accepts an iterable
def our_enumerate(some_iterable, start=0):
i = start
for element in some_iterable:
yield i, element
i += 1
这里的关键点是--yield 关键字定义了一个发生器,它是可迭代的。通过返回索引和元素本身,我们创建了一个可迭代的生成器对象,然后我们可以通过for 循环,从中提取元素(和它们的索引)。
如果你想在这里阅读更多关于
yield关键字的用法,请阅读我们的《理解 Python 的 "yield" 关键字指南》!
如果你使用our_enumerate() 函数而不是内置的函数,我们会得到大致相同的结果。
some_list = ['Looping', 'with', 'counters', 'is', 'a', 'classic!']
for idx, element in our_enumerate(some_list):
print(f'{idx}, {element}')
obj = our_enumerate(some_list)
print(f'Object type: {obj}')
这个结果是。
0, Looping
1, with
2, counters
3, is
4, a
5, classic!
Object type: <generator object our_enumerate at 0x000002750B595F48>
唯一的区别是,我们只是有一个通用的生成器对象,而不是一个更好的类名。
结论
归根结底,enumerate() 函数只是语法上的糖,包裹着一个极其普通和直接的循环实现。
在这个简短的指南中,我们看了一下 Python 中的enumerate() 函数--内置的方便方法,用来迭代一个集合,并对元素进行索引注释。