PEP 276 – 整数的简单迭代器
- 作者:
- Jim Althoff <james_althoff at i2.com>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2001 年 11 月 12 日
- Python 版本:
- 2.3
- 发布历史:
摘要
Python 2.1 添加了新功能以支持迭代器(PEP 234)。迭代器在许多编码情况下都被证明是有用和方便的。值得注意的是,Python 的 for 循环控制结构在 2.1 版本中使用了迭代器协议。还值得注意的是,Python 为以下内置类型提供了迭代器:列表、元组、字典、字符串和文件。本 PEP 建议为内置类型 int (types.IntType) 添加一个迭代器。这样的迭代器将简化 Python 中某些 for 循环的编码。
BDFL 声明
该 PEP 于 2005 年 6 月 17 日被拒绝,并附带一条发往 python-dev 的注释。
最初的大部分需求已通过 enumerate() 函数得到满足,该函数已在 Python 2.3 中获得接受。
此外,该提案既允许又鼓励了滥用,例如
>>> for i in 3: print i
0
1
2
同样,该提案会禁用以下语句中的语法错误,这是没有帮助的
x, = 1
规范
为 types.intType(即内置类型“int”)定义一个迭代器,当使用 types.intType 的实例作为参数调用内置函数“iter”时,该迭代器将被返回。
返回的迭代器具有以下行为
- 假设对象 i 是
types.intType(内置类型 int)的一个实例,并且 i > 0 iter(i)返回一个迭代器对象- 所述迭代器对象迭代整数序列 0,1,2,…,i-1
示例
iter(5)返回一个迭代器对象,该对象迭代整数序列 0,1,2,3,4 - 如果 i <= 0,
iter(i)返回一个“空”迭代器,即,在第一次调用其“next”方法时抛出 StopIteration
换句话说,所述迭代器的条件和语义与 range() 和 xrange() 函数的条件和语义一致。
请注意,与整数 i 相关联的序列 0,1,2,…,i-1 在 Python 编程的上下文中被认为是“自然的”,因为它与 Python 中序列的内置索引协议一致。例如,Python 列表和元组的索引从 0 开始,到 len(object)-1 结束(使用正索引时)。换句话说,这些对象的索引序列为 0,1,2,…,len(object)-1
基本原理
一种常见的编程习惯是获取一个对象集合,并以某种既定的顺序对集合中的每个项目应用一些操作。Python 提供了“for in”循环控制结构来处理这种常见习惯。然而,有时需要(或更方便)通过遍历每个索引并使用相应的索引访问集合中的每个项目来访问“索引”集合中的每个项目。
例如,可能有一个二维“表”对象,其中需要对表中每一行的第一列应用一些操作。根据表的实现,可能无法先访问每一行,然后将每一列作为单独的对象进行访问。相反,可能可以使用行索引和列索引访问表中的单元格。在这种情况下,需要使用一种习惯,即遍历索引序列以访问表中所需的项目。(请注意,Java-Swing-Jython 中常用的 DefaultTableModel 类就具有此协议)。
另一个常见的例子是需要并行处理两个或更多集合。另一个例子是需要访问,例如,集合中的每隔一个项目。
还有许多其他例子,其中通过对索引进行计算来方便地访问集合中的项目,从而需要访问索引而不是直接访问项目本身。
我们称这种习惯为“索引 for 循环”习惯。一些编程语言提供内置语法来处理这种习惯。在 Python 中,实现索引 for 循环习惯的常见约定是使用内置的 range() 或 xrange() 函数生成索引序列,例如
for rowcount in range(table.getRowCount()):
print table.getValueAt(rowcount, 0)
或
for rowcount in xrange(table.getRowCount()):
print table.getValueAt(rowcount, 0)
Python 社区不时讨论索引 for 循环习惯。有时有人认为,在这种设计习惯中使用 range() 或 xrange() 函数的必要性在于
- 不明显(对于 Python 新手程序员),
- 容易出错(即使对于经验丰富的 Python 程序员也很容易忘记)
- 对于那些觉得有必要理解
xrange()与range()的区别和推荐用法的人来说,令人困惑和分心 - 笨重,特别是与
len()函数结合使用时,即xrange(len(sequence)) - 不如其他语言中的等效机制方便,
- 烦人,一个“瑕疵”等。
并且时不时提出 Python 如何为这种习惯提供更好机制的建议。最近的例子包括 PEP 204,“范围字面量”,以及 PEP 212,“循环计数器迭代”。
大多数情况下,此类提案包括对 Python 语法和其他“重量级”更改。
这里的困难部分在于,提倡新语法意味着为“通用索引”提供一个全面的解决方案,该解决方案必须包括以下方面:
- 起始索引值
- 结束索引值
- 步长值
- 开区间与闭区间与半开区间
找到一种全面、简单、通用、符合 Python 风格、受许多人欢迎、易于实现、不与现有结构冲突、不过度重载现有结构等新语法,事实证明比人们预期的更困难。
本 PEP 中概述的提案试图通过提出一种简单的“轻量级”解决方案来解决这个问题,该解决方案通过使用一个已经可用的(从 Python 2.1 开始)经过验证的机制来帮助最常见的情况:即迭代器。
由于 for 循环从 Python 2.1 开始已经使用“迭代器”协议,因此按照本 PEP 的提议为 types.IntType 添加迭代器将默认启用以下用于索引 for 循环习惯的快捷方式
for rowcount in table.getRowCount():
print table.getValueAt(rowcount, 0)
与当前使用 range() 或 xrange() 函数的机制相比,这种方法被认为具有以下优点:
- 更简单,
- 更整洁,
- 专注于当前问题,无需借助次要的面向实现的函数(
range()和xrange())
与其他变更提案相比
- 无需新语法
- 无需新关键字
- 利用了新的、完善的迭代器机制
通常
- 与已包含(从 Python 2.1 开始)的基于迭代器的“便利”更改一致,适用于其他内置类型,例如:列表、元组、字典、字符串和文件。
向后兼容性
提议的机制通常向后兼容,因为它既不需要新语法,也不需要新关键字。所有现有、有效的 Python 程序都应继续 unmodified 工作。
然而,本提案并非完全向后兼容,因为某些目前无效的语句,在本提案下将变得有效。
Tim Peters 指出了两个这样的例子
- 常见的情况是忘记包含
range()或xrange(),例如for rowcount in table.getRowCount(): print table.getValueAt(rowcount, 0)
在 Python 2.2 中会引发 TypeError 异常。
根据当前提案,上述语句将是有效的,并且将按(大概)预期工作。大概,这是一件好事。
正如 Tim 指出的那样,这是“忘记 range”的常见错误(当前通过添加对
range()或xrange()的调用来纠正)。 - (希望)非常罕见的情况是,在使用元组解包时打字错误。例如
x, = 1
在 Python 2.2 中会引发
TypeError异常。根据当前提案,上述语句将是有效的,并将 x 设置为 0。PEP 作者没有关于这种输入错误有多常见,以及在当前提案下捕获这种错误会有多困难的数据。他认为它不会频繁发生,而且如果发生,纠正起来会相对容易。
问题
关于 PEP 276 在 Python 邮件列表上的广泛讨论表明了各种意见:有些赞成,有些中立,有些反对。那些赞成的人倾向于同意上述关于整数简单迭代器的有用性、便利性、易学性和简单性的主张。
PEP 276 的问题包括
- 使用 range/xrange 很好。
答复:有些发帖人有这种感觉。其他人不同意。
- 有些人认为,对于整数 n,遍历序列“0, 1, 2, …, n-1”是不直观的。例如,有人认为“for i in 5:”是“不明显的”。有些人不喜欢这种用法,因为他们觉得它没有“正确的感觉”。有些人不喜欢它,因为他们认为这种用法迫使人们将整数视为序列,这在他们看来是错误的。有些人不喜欢它,因为他们更喜欢将 for 循环视为处理显式序列而不是任意迭代器。
答复:有些人喜欢提议的习惯用法,并认为它简单、优雅、易学且易用。有些人对此问题持中立态度。另一些人,如上所述,不喜欢它。
- 是否明显
iter(5)映射到序列 0,1,2,3,4?答复:鉴于如上所述,Python 有一个强烈的约定,即序列索引从 0 开始,并在(包括)其值比序列长度小一的索引处停止,因此有人认为所提议的序列对于 Python 程序员来说是相当直观的,同时也是有用和实用的。更重要的是,有人认为一旦学习了,这个约定就很容易记住。请注意,range 函数的文档字符串提到了
range(n)与长度为 n 的列表的索引之间自然且有用的关联。 - 可能的歧义
for i in 10: print i
可能会误认为是
for i in (10,): print i
回应:这与当前 Python 中的字符串情况完全相同(例如,将上面的 10 替换为“spam”)。
- 过于通用:在最新版本的 Python 中,存在一些上下文——例如 for 循环——迭代器会被隐式调用。有些人担心在某个上下文中(不包括 for 循环)为整数调用迭代器可能会导致意外行为和错误。上面提到的“x, = 1”示例就是一个很好的例子。
答复:从作者的角度来看,在 PEP 276 讨论中发现的上述示例似乎不是会意外误用导致微妙且难以检测的错误的情况。
此外,似乎有一种方法可以解决这个问题,通过使用本提案规范部分中概述的变体。与其向 int 类添加
__iter__方法,不如修改 for 循环处理代码以(本质上)从for i in n: # when isinstance(n,int) is 1
到
for i in xrange(n):
这种方法在 for 循环中会产生与
__iter__方法相同的结果,但会阻止在任何其他上下文中对整数值进行迭代。例如,列表和元组没有__iter__,并且通过特殊代码处理。整数值将是另一个特殊情况。 - “i in n”看起来非常不自然。
答复:有些人认为“i in len(mylist)”很容易理解和有用。有些人不喜欢它,尤其是当使用字面量时,例如“i in 5”。如果实现了前面问题答复中提到的变体,则此问题无关紧要。如果没有,那么也可以通过在 int 类中定义一个
__contains__方法来解决此问题,该方法将始终引发 TypeError。这将使“i in n”的行为与当前 Python 的行为相同。 - 可能会劝退新手使用索引 for 循环习惯用法,而标准的“for item in collection:”习惯用法显然更好。
答复:当标准习惯用法适用时,它非常出色,既不需要额外的“奖励”,也不需要“惩罚”。另一方面,确实注意到标准习惯用法过度使用/误用的情况(很可能是由于索引 for 循环习惯用法的笨拙),例如
for item in sequence: print sequence.index(item)
- 为什么不提议更大的改变呢?
对 PEP 276 的大部分异议来自那些倾向于对 Python 进行更大改动的人,以解决更通用的问题,即指定整数序列,其中此类规范足够通用,可以处理序列的起始值、结束值和步长值,并解决开区间、闭区间和半开(半闭)整数区间的变体。讨论了许多此类建议。
这些包括
- 添加 Haskell 风格的符号来指定字面量列表中的整数序列,
- 切片符号的各种用法来指定序列,
- 更改 for-in 循环的语法以允许在循环头中使用关系运算符,
- 创建整数区间类以及重载关系运算符或除法运算符以提供整数区间对象上的“切片”的方法,
- 等等。
应该指出的是,虽然有很多争论,但对于这些大规模的建议并没有压倒性的共识。
显然,PEP 276 没有提出如此大规模的更改,而是专注于一个特定的问题领域。在讨论期结束时,一些发帖人表示赞成 PEP 276 的狭隘关注点和简洁性,而不是那些更雄心勃勃的建议。对于任何此类更大规模的替代建议,似乎确实存在需要一个 PEP 的共识。鉴于这一认识,此处不再进一步讨论各种替代建议的细节。
实施
目前没有实现,但预计会很简单。然而,作者已经实现了一个带有 __iter__ 方法(用 Python 编写)的 int 子类,以此来测试本提案中的想法。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0276.rst