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

作者: 李禾

更新时间:2022-03-26 14:30:44

3235 阅读

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

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

在 Python 中,for循环通常被写成对可迭代对象的循环。这意味着您不需要计数变量来访问迭代中的项目。但有时,您确实希望有一个在每次循环迭代中都会发生变化的变量。您可以使用 Python enumerate()来同时从可迭代对象中获取计数器和值,而不是自己创建和增加变量!

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

  • 用于enumerate()在循环中获取计数器
  • 适用enumerate()于显示项目计数
  • enumerate()与条件语句一起使用
  • 实现自己的同等功能,以enumerate()
  • 解包返回的值enumerate()

让我们开始吧!

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

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

>>>
>>> values = ["a", "b", "c"]

>>> for value in values:
...     print(value)
...
a
b
c

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

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

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

>>>
>>> index = 0

>>> for value in values:
...     print(index, value)
...     index += 1
...
0 a
1 b
2 c

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

>>>
>>> index = 0

>>> for value in values:
...     print(index, value)
...
0 a
0 b
0 c

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

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

>>>
>>> for index in range(len(values)):
...     value = values[index]
...     print(index, value)
...
0 a
1 b
2 c

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

在此示例中,可能发生的一个常见错误是您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 a
1 b
2 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 a
2 b
3 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. 不要太担心这个函数如何检查问题。关键是要展示在现实世界中的使用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 User
Real User 1
Real 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_elem
10
>>> 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 g
b e h
c 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 g
1 b e h
2 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 g
1 b e h
2 c f i

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

结论

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

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

  • enumerate()在for循环中使用 Python
  • 应用enumerate()在几个现实世界的例子中
  • enumerate()使用参数解包获取值
  • 实现自己的同等功能,以enumerate()

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

 

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

版权声明:本文著作权归作者【李禾 】所有,不代表本网站立场。

侵权请联系:root_email@163.com

相关推荐