写点什么

Python enumerate():使用计数器简化循环

  • 2021 年 12 月 10 日
  • 本文字数:7590 字

    阅读完需:约 25 分钟

摘要:当您需要计数和迭代中的值时,Pythonenumerate()允许您编写 Pythonicfor 循环。最大的优点 enumerate()是它返回一个带有计数器和值的元组,因此您不必自己增加计数器。它还为您提供了更改计数器起始值的选项。

 

本文分享自华为云社区《Pythonenumerate():使用计数器简化循环》,作者:Yuchuan。

 

在 Python 中,for 循环通常被写成对可迭代对象的循环。这意味着您不需要计数变量来访问迭代中的项目。但有时,您确实希望有一个在每次循环迭代中都会发生变化的变量。您可以使用 Python

enumerate()来同时从可迭代对象中获取计数器和值,而不是自己创建和增加变量!


在本教程中,您将看到如何:

  • 用于 enumerate()在循环中获取计数器

  • 适用 enumerate()于显示项目计数

  • enumerate()与条件语句一起使用

  • 实现自己的同等功能,以 enumerate()

  • 解包返回的enumerate()

让我们开始吧!

for 在 Python 中使用循环进行迭代


forPython 中的循环使用基于集合的迭代。这意味着 Python 在每次迭代时将迭代中的下一项分配给循环变量,如下例所示:

>>> 


>>> values = ["a", "b", "c"]
>>> for value in values:... print(value)...abc
复制代码


​在这个例子中,values 是一个名单有三个字符串,"a","b",和"c"。在 Python 中,列表是一种可迭代对象。在 for 循环中,循环变量是 value。在循环的每次迭代中,value 设置为 的下一项 values。


接下来,您打印 value 到屏幕上。基于集合的迭代的优势在于它有助于避免其他编程语言中常见的逐一错误。


现在想象一下,除了值本身之外,您还想在每次迭代时将列表中项目的索引打印到屏幕上。处理此任务的一种方法是创建一个变量来存储索引并在每次迭代时更新它:

>>> 


>>> index = 0
>>> for value in values:... print(index, value)... index += 1...0 a1 b2 c
复制代码


​在本例中,index 是一个整数,用于跟踪您在列表中的距离。在循环的每次迭代中,您打印 index 以及 value. 循环的最后一步是将存储的数字更新 index 一。当您忘记 index 在每次迭代时更新时,会出现一个常见错误:

>>> 


>>> index = 0
>>> for value in values:... print(index, value)...0 a0 b0 c
复制代码


​在这个例子中,index 在 0 每次迭代时都保持 at ,因为没有代码在循环结束时更新它的值。特别是对于长或复杂的循环,这种错误是出了名的难以追踪。


解决此问题的另一种常见方法是使用 range()结合 len()自动创建索引。这样,您就不需要记住更新索引:

>>> 


>>> for index in range(len(values)):...     value = values[index]...     print(index, value)...0 a1 b2 c
复制代码


​在本例中,len(values)返回 values 的长度,即 3。然后 range()创建一个迭代器,从默认的起始值开始运行,0 直到它达到 len(values)负一。在这种情况下,index 成为您的循环变量。在循环中,您将当前值设置为 value 等于 中的项目。最后,您打印和。values index index value


在此示例中,可能发生的一个常见错误是您 value 在每次迭代开始时忘记更新。这类似于之前忘记更新索引的错误。这是该循环不被视为 Pythonic 的原因之一。


这个例子也有一些限制,因为 values 必须允许使用整数索引访问它的项目。允许这种访问的可迭代对象在 Python 中称为序列


技术细节:根据 Python 文档,可迭代对象是可以一次返回一个成员的任何对象。根据定义,可迭代对象支持迭代器协议,该协议指定在迭代器中使用对象时如何返回对象成员。Python 有两种常用的可迭代类型:

1.    序列

2.    发电机


任何可迭代对象都可以在 for 循环中使用,但只能通过整数索引访问序列。尝试通过生成器或迭代器的索引访问项目将引发 TypeError:

>>> 


>>> enum = enumerate(values)>>> enum[0]Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: 'enumerate' object is not subscriptable
复制代码


​在此示例中,您将返回值分配 enumerate()给 enum。enumerate()是一个迭代器,因此尝试通过索引访问其值会引发 TypeError.幸运的是,Pythonenumerate()可以让您避免所有这些问题。它是一个内置函数,这意味着自从 2003 年在 Python 2.3 中添加它以来,它在每个版本的 Python 中都可用。

使用 Python 的 enumerate()


您可以 enumerate()以与使用原始可迭代对象几乎相同的方式在循环中使用。不是将可迭代对象直接 in 放在 for 循环之后,而是将它放在 enumerate(). 您还必须稍微更改循环变量,如下例所示:

>>> 


>>> for count, value in enumerate(values):...     print(count, value)...0 a1 b2 c
复制代码


​当您使用 enumerate()时,该函数会返回两个循环变量:

1. 该计数当前迭代的

2. 当前迭代中项目的


就像普通 for 循环一样,循环变量可以任意命名。您在本例中使用 count 和 value,但它们可以命名为 i 和/v 或任何其他有效的 Python 名称。


使用 enumerate(),您不需要记住从可迭代对象访问该项目,并且您不需要记住在循环结束时推进索引。一切都由 Python 的魔力自动为您处理!


技术细节:使用两个循环变量 count 和 value,用逗号分隔是参数解包的一个例子。本文稍后将进一步讨论这个强大的 Python 特性。


Pythonenumerate()有一个额外的参数,您可以使用它来控制计数的起始值。默认情况下,起始值是 0 因为 Python 序列类型从零开始索引。换句话说,当您想要检索列表的第一个元素时,您可以使用 index 0:

>>> 


>>> print(values[0])a
复制代码


​您可以在此示例中看到,使用 values 索引访问 0 会给出第一个元素 a。但是,很多时候您可能不希望从 enumerate()开始计数 0。例如,您可能希望打印一个自然计数数作为用户的输出。在这种情况下,您可以使用 start 参数 forenumerate()来更改起始计数:

>>> 


>>> for count, value in enumerate(values, start=1):...     print(count, value)...1 a2 b3 c
复制代码


​在本例中,您传递 start=1,它从第一次循环迭代 count 的值开始 1。将此与前面的示例进行比较,其中 start 的默认值为 0,看看您是否能发现差异。

用 Python 练习 enumerate()


您应该 enumerate()在需要在循环中使用计数和项目的任何时候使用。请记住,enumerate()每次迭代都会将计数加一。但是,这只是略微限制了您的灵活性。由于计数是标准的 Python 整数,因此您可以通过多种方式使用它。在接下来的几节中,您将看到 enumerate().


可迭代项的自然计数


在上一节中,您看到了如何使用 enumerate()withstart 创建一个自然计数数字来为用户打印。enumerate()在 Python 代码库中也像这样使用。您可以在脚本中看到一个示例,它读取 reST 文件并在出现格式问题时告诉用户。


注意: reST,也称为 reStructured Text,是 Python 用于文档的文本文件的标准格式。您经常会看到在 Python 类和函数中包含作为文档字符串的 reST 格式的字符串。读取源代码文件并告诉用户格式问题的脚本称为 linter,因为它们在代码中寻找隐喻的 lint。


这个例子是从rstlint.py. 不要太担心这个函数如何检查问题。关键是要展示在现实世界中的使用 enumerate()


1def check_whitespace(lines): 2    """Check for whitespace and line length issues.""" 3    for lno, line in enumerate(lines): 4        if "\r" in line: 5            yield lno+1, "\\r in line" 6        if "\t" in line: 7            yield lno+1, "OMG TABS!!!1" 8        if line[:-1].rstrip(" \t") != line[:-1]: 9            yield lno+1, "trailing whitespace"
复制代码


​check_whitespace()接受一个参数,lines,它是应该评估的文件行。在 的第三行 check_whitespace(),enumerate()用于循环 over lines。这将返回行号,缩写为 lno 和 line。由于 start 未使用,因此 lno 是文件中行的从零开始的计数器。check_whitespace()然后对不合适的字符进行多次检查:

1. 回车 ( \r)

2. 制表符 ( \t)

3. 行尾的任何空格或制表符


当这些项目之一存在时,为用户 check_whitespace() 产生当前行号和有用的消息。计数变量 lno 已 1 添加到其中,以便它返回计数行号而不是从零开始的索引。当 的用户 rstlint.py 阅读消息时,他们将知道要转到哪一行以及要修复的内容。


跳过项目的条件语句


使用条件语句来处理项目是一种非常强大的技术。有时您可能只需要在循环的第一次迭代上执行操作,如下例所示:

>>> 


>>> users = ["Test User", "Real User 1", "Real User 2"]>>> for index, user in enumerate(users):...     if index == 0:...         print("Extra verbose output for:", user)...     print(user)...Extra verbose output for: Test UserReal User 1Real User 2
复制代码


​在此示例中,您将列表用作用户的模拟数据库。第一个用户是您的测试用户,因此您希望打印有关该用户的额外诊断信息。由于您已将系统设置为首先测试用户,因此您可以使用循环的第一个索引值来打印额外的详细输出。


您还可以将数学运算与计数或索引的条件结合起来。例如,您可能需要从可迭代对象中返回项目,但前提是它们具有偶数索引。您可以使用 enumerate()以下方法执行此操作:

>>> 


>>> def even_items(iterable):...     """Return items from ``iterable`` when their index is even."""...     values = []...     for index, value in enumerate(iterable, start=1):...         if not index % 2:...             values.append(value)...     return values...
复制代码


​even_items()接受一个名为 的参数,iterable 它应该是 Python 可以循环遍历的某种类型的对象。首先,values 被初始化为一个空列表。然后你用和 set 创建一个 for 循环。iterableenumerate()start=1 内 for 循环,你检查除以余下是否 index 通过 2 为零。如果是,则将该项目附加到 values. 最后,您返回 values。


您可以使用列表推导式在一行中执行相同的操作,而无需初始化空列表,从而使代码更加 Pythonic:

>>> 


>>> def even_items(iterable):...     return [v for i, v in enumerate(iterable, start=1) if not i % 2]...
复制代码


​在此示例代码中,even_items()使用列表推导式而不是 for 循环从列表中提取索引为偶数的每个项目。


您可以 even_items()通过从 1 到的整数范围中获取偶数索引项来验证它是否按预期工作 10。结果将是[2, 4, 6, 8, 10]:

>>> 


>>> seq = list(range(1, 11))
>>> print(seq)[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> even_items(seq)[2, 4, 6, 8, 10]
复制代码


​正如预期的那样,从 even_items()返回偶数索引项 seq。当您使用整数时,这不是获得偶数的最有效方法。但是,现在您已经验证它 even_items()可以正常工作,您可以获得 ASCII 字母表的偶数索引字母:

>>> 


>>> alphabet = "abcdefghijklmnopqrstuvwxyz"
>>> even_items(alphabet)['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z']
复制代码


​alphabet 是一个字符串,它包含 ASCII 字母表的所有 26 个小写字母。调用 even_items()和传递 alphabet 返回字母表中交替字母的列表。


Python 字符串是序列,可用于循环以及整数索引和切片。因此,对于字符串,您可以使用方括号 even_items()更有效地实现相同的功能:

>>> 


>>> list(alphabet[1::2])['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z']
复制代码


​在这里使用字符串切片,你给出起始索引 1,它对应于第二个元素。第一个冒号之后没有结束索引,因此 Python 会转到字符串的末尾。然后添加第二个冒号,后跟 a,2 以便 Python 将采用所有其他元素。


但是,正如您之前看到的,生成器和迭代器不能被索引或切片,因此您仍然会发现它们 enumerate()很有用。要继续上一个示例,您可以创建一个生成器函数,根据需要生成字母表中的字母:

>>> 


>>> def alphabet():...     alpha = "abcdefghijklmnopqrstuvwxyz"...     for a in alpha:...         yield a
>>> alphabet[1::2]Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: 'function' object is not subscriptable
>>> even_items(alphabet())['b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z']
复制代码


​在此示例中,您定义 alphabet()了一个生成器函数,当该函数在循环中使用时,它会一个一个地生成字母表中的字母。Python 函数,无论是生成器还是常规函数,都无法通过方括号索引访问。你在第二行试试这个,它会引发一个 TypeError.不过,您可以在循环中使用生成器函数,并且您可以在最后一行传递 alphabet()给 even_items(). 可以看到结果和前面两个例子是一样的。

理解 Python enumerate()


在最后几节中,您看到了何时以及如何 enumerate()发挥优势的示例。现在您已经掌握了 的实际方面 enumerate(),您可以了解更多有关该函数如何在内部工作的信息。


为了更好地了解 enumerate()工作原理,您可以使用 Python 实现您自己的版本。您的版本 enumerate()有两个要求。这应该:

1. 接受一个可迭代和一个起始计数值作为参数

2. 发回一个包含当前计数值和可迭代对象相关项的元组

Python 文档中给出了一种编写满足这些规范的函数的方法:

>>> 


>>> def my_enumerate(sequence, start=0):...     n = start...     for elem in sequence:...         yield n, elem...         n += 1...
复制代码


​my_enumerate()接受两个参数,sequence 和 start。默认值 start 是 0。在函数定义中,您初始化 n 为 的值 start 并 for 在 sequence.对于每一个 elem 在 sequence 你 yield 控制返回给调用位置和发送回的当前值 n 和 elem。最后,您递增 n 以准备下一次迭代。您可以 my_enumerate()在此处查看实际操作:

>>> 


>>> seasons = ["Spring", "Summer", "Fall", "Winter"]
>>> my_enumerate(seasons)<generator object my_enumerate at 0x7f48d7a9ca50>
>>> list(my_enumerate(seasons))[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(my_enumerate(seasons, start=1))[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
复制代码


​首先,您创建要使用的四个季节的列表。接下来,您将展示调用 my_enumerate()with seasonsassequence 创建一个生成器对象。这是因为您使用 yield 关键字将值发送回调用者。


最后,创建两个列表 my_enumerate(),在其中起始值被保留为默认,0 在其中,一个 start 改变为 1。在这两种情况下,您最终都会得到一个元组列表,其中每个元组的第一个元素是计数,第二个元素是来自 的值 seasons。


尽管您 enumerate()只需几行 Python 代码即可实现等效的函数,但实际的代码 enumerate()是用 C 编写的。这意味着它超级快速和高效。

解包参数 enumerate()


当您 enumerate()在 for 循环中使用时,您告诉 Python 使用两个变量,一个用于计数,另一个用于值本身。您可以通过使用称为参数解包的 Python 概念来做到这一点。


参数解包的思想是,一个元组可以根据序列的长度分成几个变量。例如,您可以将包含两个元素的元组解包为两个变量:

>>> 


>>> tuple_2 = (10, "a")>>> first_elem, second_elem = tuple_2>>> first_elem10>>> second_elem'a'
复制代码


​首先,您创建一个包含两个元素的元组,10 和"a"。然后将该元组解包到 first_elemand 中 second_elem,每个都从元组中分配一个值。


当您调用 enumerate()并传递一系列值时,Python 会返回一个迭代器。当您向迭代器询问其下一个值时,它会生成一个包含两个元素的元组。元组的第一个元素是计数,第二个元素是您传递的序列中的值:

>>> 


>>> values = ["a", "b"]>>> enum_instance = enumerate(values)>>> enum_instance<enumerate at 0x7fe75d728180>>>> next(enum_instance)(0, 'a')>>> next(enum_instance)(1, 'b')>>> next(enum_instance)Traceback (most recent call last):  File "<stdin>", line 1, in <module>StopIteration
复制代码


​在此示例中,您创建了一个列表,该列表 values 包含两个元素"a"和"b"。然后传递 values 给 enumerate()并将返回值分配给 enum_instance. 当您打印时 enum_instance,您可以看到它是一个 enumerate()具有特定内存地址的实例。


然后使用 Python 的内置 next()函数从 enum_instance. enum_instance 返回的第一个值是一个元组,其中包含计数 0 和来自 的第一个元素 values,即"a"。


next()再次调用 on 会 enum_instance 产生另一个元组,这次是计数 1 和来自 values,的第二个元素"b"。最后,由于没有更多的值要从 返回,所以再调用 next()一次会增加。StopIterationenum_instance


在 for 循环中使用可迭代对象时,Python 会 next()在每次迭代开始时自动调用,直到 StopIteration 引发。Python 将从可迭代对象中检索到的值分配给循环变量。


如果可迭代对象返回一个元组,则可以使用参数解包将元组的元素分配给多个变量。这是您在本教程前面通过使用两个循环变量所做的。


另一次您可能已经看到使用 for 循环解包参数是使用内置的 zip(),它允许您同时迭代两个或多个序列。在每次迭代中,zip()返回一个元组,该元组从所有传递的序列中收集元素:

>>> 


>>> first = ["a", "b", "c"]>>> second = ["d", "e", "f"]>>> third = ["g", "h", "i"]>>> for one, two, three in zip(first, second, third):...     print(one, two, three)...a d gb e hc f i
复制代码


​通过使用 zip(),可以遍历 first,second 以及 third 在同一时间。在 for 循环中,您分配元素 from firstto one、 from secondtotwo 和 from thirdto three。然后打印三个值。


您可以组合 zip()和 enumerate()使用嵌套参数解包:

>>> 


>>> for count, (one, two, three) in enumerate(zip(first, second, third)):...     print(count, one, two, three)...0 a d g1 b e h2 c f i
复制代码


​在 for 此示例的循环中,您嵌套 zip()在 enumerate(). 这意味着每次 for 循环迭代时,都会 enumerate()产生一个元组,其中第一个值作为计数,第二个值作为另一个元组,其中包含从参数到 的元素 zip()。要解压嵌套结构,您需要添加括号以从 zip().


还有其他方法可以模拟 enumerate()与 zip(). 一种方法使用 itertools.count(),它默认返回从零开始的连续整数。您可以将前面的示例更改为使用 itertools.count():

>>> 


>>> import itertools>>> for count, one, two, three in zip(itertools.count(), first, second, third):...     print(count, one, two, three)...0 a d g1 b e h2 c f i
复制代码


​用 itertools.count()在这个例子中,您可以使用一个单一的 zip()呼叫产生计数以及没有嵌套参数拆包的循环变量。

结论


当您需要计数和迭代中的值时,Pythonenumerate()允许您编写 Pythonicfor 循环。最大的优点 enumerate()是它返回一个带有计数器和值的元组,因此您不必自己增加计数器。它还为您提供了更改计数器起始值的选项。


在本教程中,您学习了如何:

  • enumerate()在 for 循环中使用 Python

  • 应用 enumerate()在几个现实世界的例子中

  • enumerate()使用参数解包获取值

  • 实现自己的同等功能,以 enumerate()


您还看到 enumerate()在一些实际代码中使用,包括在 CPython 代码存储库中。您现在拥有简化循环并使 Python 代码时尚的超能力!


点击关注,第一时间了解华为云新鲜技术~

发布于: 2 小时前阅读数: 5
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
Python enumerate():使用计数器简化循环