乐趣区

关于python:联合迭代器与生成器enumerate-内置函数真香

花下猫语:Python 中很多内置函数的作用都十分大,比如说 enumerate() 和 zip(),它们使得咱们在作迭代操作时极为棘手。这是一篇很多年前的 PEP,提议在 Python 2.3 版本中引入 enumerate(),该文档整合了其它几篇 PEP 的想法(包含过后新引入的迭代器与生成器),提出了更好的实现计划以及函数名。通过这么多年的倒退,enumerate() 不可避免地有了一些变动,但不变的是,它跟 19 年前一样,还是很有必要、很好用,用着真香!

PEP 原文: https://www.python.org/dev/peps/pep-0279

PEP 题目: The enumerate() built-in function

PEP 作者: Raymond Hettinger

创立日期: 2002-06-30

合入版本: 2.3

译者:豌豆花下猫 @Python 猫

PEP 翻译打算:https://github.com/chinesehuazhou/peps-cn

摘要

本 PEP 引进了一个新的内置函数 enumerate() 来简化罕用的循环写法。它为所有的可迭代对象赋能,作用就像字典的 iteritems() 那样——一种紧凑、可读、牢靠的索引表示法。

基本原理

Python 2.2 在 PEP 234[3] 中提出了可迭代对象接口的概念。iter() 工厂函数作为一种通用的调用约定而被提出,深刻批改了迭代器的应用形式,作为整个 Python 的对立标准。这种对立的标准就是为映射类型、序列类型和文件对象建设一个通用的可迭代对象接口。

PEP 255[1] 中提出的生成器是作为一种更容易创立迭代器的办法引入的,特地是具备简单的外部执行过程或变量状态的迭代器。有了生成器当前,PEP 212[2] 中对于循环的计数器的想法就有可能改良了。

那些想法是提供一种洁净的迭代语法,带有索引和值,但不适用于所有的可迭代对象。而且,那种办法没有生成器提供的内存敌对的长处(生成器不会一次性计算整个序列)。

(Python 猫注:对于生成器的 PEP 也有翻译,请查看 PEP-255、PEP-342、PEP-380)

新的提议是增加一个内置函数 enumerate(),在有了迭代器和生成器当前,它就能够实现。它为所有的可迭代对象赋能,作用就像字典的 iteritems() 那样——一种紧凑、可读、牢靠的索引表示法。像 zip() 一样,它无望成为一种罕用的循环习语(idiom)。

(Python 猫注:zip() 函数十分强,举荐浏览《一篇文章把握 Python 内置 zip() 的全部内容》)

这一提议的目标是利用现有的实现,再加一点点的致力来整合。它是向后兼容的,不须要新的关键字。本提案将合入 Python 2.3,不须要从 \_\_future\_\_ 中导入。

新内置函数的标准

def enumerate(collection):
   'Generates an indexed series:  (0,coll[0]), (1,coll[1]) ...'
   i = 0
   it = iter(collection)
   while 1:
      yield (i, it.next())
      i += 1

注 A :PEP 212 循环计数器迭代 [2] 探讨了几个实现索引的提议。有些提议只实用于列表,不像下面的函数实用于任意生成器、xrange、序列或可迭代对象。

另外,那些提议是在 Python 2.2 之前提出并评估的,然而 Python 2.2 没有蕴含生成器。因而,PEP 212 中的非生成器版本有一个毛病,即会用一个微小的元组列表,导致耗费太多内存。

这里提供的生成器版本疾速且轻便,实用于所有可迭代对象,并容许用户在不节约计算量的状况下中途放弃。

还有一些波及相干问题的 PEP:整型迭代器、整型 for 循环,以及一个批改 range 和 xrange 的参数的 PEP。enumerate() 提案并不排挤其它提案,即便那些提案被驳回,它依然满足一个重要的需要——对任意可迭代对象中的元素进行计数的需要。

其它的提案给出了一种产生索引的办法,但没有相应的值。如果给定的序列不反对随机拜访,比方文件对象、生成器或用 \_\_getitem\_\_定义的序列,这就特地成问题。

注 B :简直所有的 PEP 审阅人都欢送这个函数,但对于“是否应该把它作为内置函数”

存在一致。一方提议应用独立的模块,次要理由是减缓语言收缩的速度。

另一方提议应用内置函数,次要理由是该函数合乎 Python 外围编程格调,实用于任何具备可迭代接口的对象。正如 zip() 解决了在多个序列上循环的问题,enumerate() 函数解决了循环计数器的问题。

如果只容许加一个内置函数,那么 enumerate() 就是最重要的通用工具,能够解决最宽泛的问题,同时进步程序的简洁性、清晰度和可靠性。

注 C :探讨了多种备选名称:

函数名 剖析
iterindexed() 五个音节太拗口了
index() 很好的动词,然而可能会跟 .index () 办法混同
indexed() 很受欢迎,然而应该防止形容词
indexer() 在 for 循环中,名词读起来不太好
count() 间接而明确,但罕用于其它语境
itercount() 间接、明确,但被不止一个人厌恶
iteritems() 与字典的 key:value 概念抵触
itemize() 让人困惑,因为 amap.items() != list(itemize(amap))
enum() 简练;不迭 enumerate 分明;与其它语言中的枚举太类似,但有着不同的含意

所有波及“count”的名称还有一个毛病,即隐含着计数是从 1 开始而不是从 0 开始的意思。

所有波及“index”的名称与数据库语言的用法抵触,数据库的索引示意一种排序操作,但不是线性排序。

注 D: 在最后的提案中,这个函数带有可选的 start 和 stop 参数。GvR 指出,函数 enumerate(seqn,4,6) 还有一种看似正当的解释,即返回序列的第 4 和第 5 个元素的切片。为了防止歧义,这两个可选参数被摘掉了,只管这意味着循环计数器失去了局部的灵活性。

在从 1 开始计数的常见用例中,这种可选参数的写法很有用,比方:

for linenum, line in enumerate(source,1):
    print linenum, line

(Python 猫注:这篇文档说 enumerate 没有起止参数,然而,在后续版本中(例如我用的 3.9),它反对应用一个可选的 start 参数!我暂未查到这个变更是在何时退出的,如有知情者,烦请告知我,以便修改!)

GvR 评论道:

filter 和 map 应该 die,被纳入列表推导式,不减少更多的变体。我宁肯引进做迭代器运算的内置函数(例如 iterzip,我常常举的例子)。
我认可用某种办法并行地遍历序列及其索引的想法。把它作为一个内置函数,没有问题。
我不喜爱“indexed”这个名字;形容词不是好的函数名。能够用 iterindexed() ?

Ka-Ping Yee 评论道:

我对你的提议也很称心……新增的内置函数(偏向于用“indexed”)是我期盼了很久的货色。

Neil Schemenauer 评论道:

新的内置函数听起来不错。Guido 可能会放心减少太多内置对象。你最好把它们作为某个模块的一部分。如果你用模块的话,那么你能够增加很多有用的函数(Haskell 有很多,咱们能够去“偷”)。

Magnus Lie Hetland 评论道:

我认为 indexed 会是一个有用和天然的内置函数。我必定会常常应用它。
我十分喜爱 indexed();+1。很快乐它淘汰了 PEP-281。为迭代器增加一个独自的模块仿佛是个好主见。

来自社区的反馈:

对于 enumerate() 提案,简直 100% 赞成。简直所有人都喜爱这个想法。

作者的正文:

在这些评论之前,共有四种内置函数被提出来。通过评论之后,xmap、xfilter 和 xzip 被撤销了。剩下的一个对 Python 来说是至关重要的。Indexed() 非常容易实现,并且立马就能够写进文档。更重要的是,它在日常编程中很有用,如果不必它,就须要显式地应用生成器。
这个提案最后蕴含了另一个函数 iterzip()。但之后在 itertools 模块中实现成了一个 izip() 函数。

参考资料

1、PEP 255 Simple Generators http://www.python.org/dev/peps/pep-0255

2、(1, 2) PEP 212 Loop Counter Iteration http://www.python.org/dev/peps/pep-0212

3、PEP 234 Iterators http://www.python.org/dev/peps/pep-0234

版权

本文档曾经进入公共畛域。源文档:

https://github.com/python/peps/blob/master/pep-0279.txt

退出移动版