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']
这种范围表示法由零个、一个或两个由冒号分隔的索引组成。第一个索引是*起始*索引,第二个是*结束*索引。当两者中的任何一个被省略时,它们分别默认为序列的起始和结束。
还有一种扩展的范围表示法,它也包含*步长*。尽管这种表示法目前不受大多数内置类型的支持,但如果支持,它将如下所示
>>> 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