Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 204 – 范围字面量

作者:
Thomas Wouters <thomas at python.org>
状态:
已拒绝
类型:
标准跟踪
创建日期:
2000年7月14日
Python 版本:
2.0
发布历史:


目录

Warning

本 PEP 已被拒绝。

×

经过仔细考虑和一段时间的沉思,本提案已被拒绝。开放性问题,以及范围和切片语法之间的一些混淆,引起了足够多的疑问,以至于 Guido 没有在 Python 2.0 中接受它,后来完全拒绝了该提案。新的语法及其意图被认为不够明确。

[待定:Guido,请修改/确认此内容。最好两者都做;这是一个 PEP,它应该包含拒绝和/或重新考虑的*所有*原因,以供将来参考。]

引言

本 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