PEP 276 – 整数简单迭代器
- 作者:
- Jim Althoff <james_althoff at i2.com>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2001年11月12日
- Python 版本:
- 2.3
- 历史记录:
摘要
Python 2.1 添加了新的功能来支持迭代器(PEP 234)。迭代器在许多编码情况下已被证明非常有用和方便。需要注意的是,从 2.1 版本开始,Python 的 for 循环控制结构的实现使用了迭代器协议。还需要注意的是,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 的语法和其他“重量级”更改。
这里部分困难在于,提倡新的语法意味着对“通用索引”的全面解决方案,必须包括以下方面
- 起始索引值
- 结束索引值
- 步长值
- 开区间与闭区间与半开区间
找到一种新的语法,使其全面、简单、通用、Pythonic、对许多人有吸引力、易于实现、不与现有结构冲突、不过度加载现有结构等,已被证明比人们预期的更困难。
本 PEP 中概述的提案试图通过提出一个简单的“轻量级”解决方案来解决这个问题,该解决方案通过使用已经可用的经过验证的机制(截至 Python 2.1)来帮助最常见的情况:即迭代器。
因为从 Python 2.1 开始,for 循环已经使用了“迭代器”协议,所以为 types.IntType 添加迭代器(如本 PEP 中所建议的那样)将默认启用以下索引 for 循环习惯用法的快捷方式
for rowcount in table.getRowCount():
print table.getValueAt(rowcount, 0)
据称,与当前使用 range()
或 xrange()
函数的机制相比,这种方法具有以下优势
- 更简单,
- 更简洁,
- 专注于手头的问题,无需诉诸辅助的实现导向函数(
range()
和xrange()
)
并且与其他更改提案相比
- 不需要新的语法
- 不需要新的关键字
- 利用了新的且完善的迭代器机制
并且通常
- 与已经包含在内的基于迭代器的“便利”更改(截至 Python 2.1)一致,这些更改适用于其他内置类型,例如:列表、元组、字典、字符串和文件。
向后兼容性
所提出的机制通常是向后兼容的,因为它既不调用新的语法也不调用新的关键字。所有现有的有效 Python 程序都应该继续在未修改的情况下工作。
但是,该提案并非完全向后兼容,因为某些当前无效的语句在当前提案下将变为有效。
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 作者没有关于此键入错误发生频率的数据,也没有关于在当前提案下捕获此类错误的难度的数据。他认为这种情况并不经常发生,并且如果发生,相对容易更正。
问题
在 Python 兴趣邮件列表上关于 PEP 276 的广泛讨论表明意见范围广泛:一些赞成,一些中立,一些反对。赞成者倾向于同意上述关于整数简单迭代器的有用性、便利性、易学性和简单性的说法。
PEP 276 的问题包括
- 使用 range/xrange 就很好。
回复:一些发帖者这样认为。其他人不同意。
- 有些人认为,对于整数 n,迭代序列“0、1、2、…、n-1”并不直观。“for i in 5:”(例如)被认为(由一些人)“不明确”。有些人不喜欢这种用法,因为它“感觉不对”。有些人不喜欢它,因为他们认为这种类型的用法迫使人们将整数视为序列,这在他们看来是错误的。有些人不喜欢它,因为他们更喜欢将 for 循环视为处理显式序列而不是处理任意迭代器。
回应:有些人喜欢提议的习语,认为它简单、优雅、易学易用。有些人对此持中立态度。另一些人,如上所述,不喜欢它。
- 是否显而易见
iter(5)
映射到序列 0,1,2,3,4?回应:鉴于如上所述,Python 具有从 0 开始索引序列并停止于(包含)其值比序列长度小 1 的索引的强烈约定,因此有人认为,提议的序列对于 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 item in collection:” 习语明显更好时使用索引 for 循环习语。
回应:标准习语在适用时非常棒,因此既不需要额外的“胡萝卜”也不需要“棍子”。另一方面,确实会注意到标准习语的过度/误用情况(很可能是由于索引 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
上次修改时间:2023-09-09 17:39:29 GMT