PEP 204 – 范围字面量
- 作者:
- Thomas Wouters <thomas at python.org>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2000年7月14日
- Python 版本:
- 2.0
- 历史记录:
简介
此 PEP 描述了 Python 2.0 的“范围字面量”提案。此 PEP 跟踪此功能的状态和所有权,计划在 Python 2.0 中引入。它包含对该功能的描述,并概述了支持该功能所需的更改。此 PEP 总结了在邮件列表论坛中进行的讨论,并在适当的情况下提供进一步信息的 URL。此文件的 CVS 修订历史记录包含权威的历史记录。
列表范围
范围是具有固定步长的数字序列,通常用于 for 循环。Python 的 for 循环旨在直接迭代序列
>>> l = ['a', 'b', 'c', 'd']
>>> for item in l:
... print item
a
b
c
d
但是,此解决方案并非总是谨慎的。首先,在 for 循环的主体中更改序列时会出现问题,导致 for 循环跳过项目。其次,无法迭代,例如,序列中的每个第二个元素。第三,有时需要根据元素的索引处理元素,这在上述结构中不容易获得。
对于这些情况以及其他需要数字范围的情况,Python 提供了 range
内置函数,该函数创建数字列表。 range
函数接受三个参数,start、end 和 step。start 和 step 是可选的,分别默认为 0 和 1。
range
函数创建一个数字列表,从 start 开始,步长为 step,直到但不包括 end,因此 range(10)
生成一个正好包含 10 个项目的列表,数字 0 到 9。
使用 range
函数,以上示例将如下所示
>>> for i in range(len(l)):
... print l[i]
a
b
c
d
或者,从 l
的第二个元素开始,并从那时起仅处理每个第二个元素
>>> for i in range(1, len(l), 2):
... print l[i]
b
d
这种方法有几个缺点
- 目的清晰度:添加另一个函数调用,可能需要额外的算术来确定列表所需的长度和步长,不会提高代码的可读性。此外,可以通过提供具有相同名称的局部或全局变量来“隐藏”内置的
range
函数,从而有效地替换它。这可能是或可能不是预期的效果。 - 效率:因为
range
函数可以被覆盖,所以 Python 编译器无法对 for 循环做出假设,并且必须维护一个单独的循环计数器。 - 一致性:已经存在一种用于表示范围的语法,如下所示。此语法使用完全相同的参数(尽管都是可选的),以完全相同的方式使用。将此语法扩展到范围以形成“范围字面量”似乎合乎逻辑。
切片索引
在 Python 中,可以通过两种方式之一索引序列:检索单个项目或检索一系列项目。检索一系列项目会生成一个与原始序列类型相同的新的对象,其中包含原始序列中的零个或多个项目。这是使用“范围表示法”完成的
>>> l[2:4]
['c', 'd']
此范围表示法由零个、一个或两个用冒号分隔的索引组成。第一个索引是start索引,第二个是end。当任一索引省略时,它们分别默认为序列的开头和结尾。
还有一个扩展的范围表示法,它也包含step。尽管大多数内置类型目前不支持此表示法,但如果支持,则其工作方式如下
>>> l[1:4:2]
['b', 'd']
切片语法的第三个“参数”与 range()
的step参数完全相同。标准和这些扩展切片的底层机制存在足够大的差异和不一致,以至于许多类和扩展(数学包之外)未实现对扩展变体的支持。虽然应该解决这个问题,但这超出了此 PEP 的范围。
但是,扩展切片确实表明,已经存在一种完全有效且适用的语法来以解决前面提到的 range()
函数使用所有缺点的方式表示范围
- 它更清晰、更简洁的语法,已被证明既直观又易于学习。
- 它与 Python 中范围的其他用法(例如切片)一致。
- 因为它是一种内置语法,而不是内置函数,所以它不能被覆盖。这意味着查看器可以确定代码的作用,并且优化器不必担心
range()
被“隐藏”。
提出的解决方案
范围字面量的提议实现将列表字面量的语法与(扩展)切片的语法相结合,以形成范围字面量
>>> [1:10]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [:5]
[0, 1, 2, 3, 4]
>>> [5:1:-1]
[5, 4, 3, 2]
范围字面量和切片语法之间有一个细微的差别:虽然可以在切片中省略所有start、end和step,但在范围字面量中省略end没有意义。在切片中,end将默认为列表的末尾,但这在范围字面量中没有任何意义。
参考实现
提议的实现可以在 SourceForge [1] 上找到。它添加了一个新的字节码 BUILD_RANGE
,该字节码从堆栈中获取三个参数并在这些参数的基础上构建一个列表。该列表被压回堆栈。
使用新的字节码对于能够根据其他计算构建范围是必要的,这些计算的结果在编译时未知。
代码向 listobject.c
引入了两个新函数,这些函数目前徘徊在私有函数和完整 API 调用之间。
PyList_FromRange()
从 start、end 和 step 构建列表,如果发生错误则返回 NULL。其原型为
PyObject * PyList_FromRange(long start, long end, long step)
PyList_GetLenOfRange()
是一个用于确定范围长度的辅助函数。以前,它是 bltinmodule.c
中的静态函数,但现在在 listobject.c
和 bltinmodule.c
(用于 xrange
)中都需要。将其设为非静态仅仅是为了避免代码重复。其原型为
long PyList_GetLenOfRange(long start, long end, long step)
未解决的问题
- 解决范围字面量中需要end参数的差异的一个可能解决方案是允许范围语法创建“生成器”,而不是列表,例如
xrange
内置函数所做的那样。但是,生成器不是列表,例如,不可能将项目分配给生成器或追加到生成器。范围语法可以想象地扩展到包括元组(即不可变列表),然后可以安全地将其实现为生成器。这可能是一个理想的解决方案,特别是对于大型数字数组:生成器只需要很少的存储和初始化,并且在根据需要计算和创建适当数量时,性能影响很小。(待办:是否根本没有?粗略测试表明,即使在长度为 1 的范围的情况下,性能也相同)
但是,即使采用了这个想法,将第二个参数“特殊处理”,在一个语法实例中使其可选,而在其他情况下使其不可选,是否明智?
- 是否应该能够将范围语法与普通列表字面量混合使用,创建单个列表?例如
>>> [5, 6, 1:6, 7, 9]
创建
[5, 6, 1, 2, 3, 4, 5, 7, 9]
- 范围字面量如何与另一个提议的新功能“列表推导式”“列表推导式” 相互作用?具体来说,是否应该能够在列表推导式中创建列表?例如
>>> [x:y for x in (1, 2) y in (3, 4)]
此示例是否应该返回一个包含多个范围的单个列表
[1, 2, 1, 2, 3, 2, 2, 3]
还是一个列表的列表,如下所示
[[1, 2], [1, 2, 3], [2], [2, 3]]
但是,由于列表推导式的语法和语义仍在热烈讨论中,因此这些问题最好由“列表推导式”PEP 来解决。
- 范围字面量接受整数以外的对象:它对传递的对象执行
PyInt_AsLong()
,因此只要对象可以强制转换为整数,就会被接受。但是,生成的列表始终由标准整数组成。范围字面量是否应该创建传递类型的列表?对于其他内置类型(如长整数和字符串)来说,这可能很有用
>>> [ 1L : 2L<<64 : 2<<32L ] >>> ["a":"z":"b"] >>> ["a":"z":2]
但是,这可能“太神奇”,以至于不明显。它也可能给用户定义的类带来问题:即使可以找到基类并创建新实例,该实例也可能需要向
__init__
传递其他参数,从而导致创建失败。 PyList_FromRange()
和PyList_GetLenOfRange()
函数需要进行分类:它们是 API 的一部分,还是应该成为私有函数?
版权
本文档已置于公共领域。
参考文献
来源:https://github.com/python/peps/blob/main/peps/pep-0204.rst
上次修改时间:2024-04-14 20:08:31 GMT