认识Python中的enumerate()函数

115 阅读5分钟

简介

使用计数器变量/索引的循环--这是计算机科学中的经典之作!通常情况下,你会明确定义一个计数器变量/索引,并在每个循环中手动增加它,或者你会使用某种语法糖,通过增强的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() 函数--内置的方便方法,用来迭代一个集合,并对元素进行索引注释。