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

Python 增强提案

PEP 3113 – 移除元组参数解包

作者:
Brett Cannon <brett at python.org>
状态:
最终版
类型:
标准跟踪
创建日期:
2007年3月2日
Python 版本:
3.0
发布历史:


目录

摘要

元组参数解包是指在函数签名中使用元组作为参数,以便自动解包序列参数。例如:

def fxn(a, (b, c), d):
    pass

在签名中使用 (b, c) 要求函数的第二个参数是一个长度为二的序列(例如,[42, -13])。当这样的序列被传递时,它会被解包并将其值赋给参数,就像在参数中执行了语句 b, c = [42, -13] 一样。

不幸的是,Python 丰富函数签名功能中的这个特性,虽然在某些情况下很方便,但它带来的问题多于其价值。因此,本 PEP 提议在 Python 3.0 中从语言中移除它们。

为何应将其移除

内省问题

Python 具有非常强大的内省能力。这些能力延伸到函数签名。函数的调用签名没有任何隐藏的细节。通常,通过查看函数对象及其上的各种属性(包括函数的 func_code 属性),很容易找出函数签名的各种细节。

但在元组参数方面存在很大的困难。元组参数的存在是通过其名称由函数代码对象的 co_varnames 属性中的 . 和一个数字组成来表示的。这允许元组参数绑定到一个只有字节码才能知道且无法在 Python 源代码中输入的名称。但这并没有指定元组的格式:它的长度、是否存在嵌套元组等。

为了从函数中获取元组的所有细节,必须分析函数的字节码。这是因为函数中的第一个字节码实际上会将元组参数解包。假设元组参数名为 .1,并且预期解包为变量 spammonty(意味着它是元组 (spam, monty)),则函数中的第一个字节码将用于语句 spam, monty = .1。这意味着要了解元组参数的所有细节,必须查看函数的初始字节码以检测格式为 \.\d+ 的参数的元组解包,并推断出关于预期参数的任何和所有信息。inspect.getargspec 函数就是通过字节码分析来提供元组参数信息的。这不容易做到,并且对内省工具来说是一个负担,因为它们必须知道 Python 字节码的工作原理(否则这是一个不必要的负担,因为所有其他类型的参数都不需要了解 Python 字节码)。

撇开分析字节码的困难不谈,依赖 Python 字节码还存在另一个问题。IronPython [3] 不使用 Python 的字节码。由于它基于 .NET 框架,它 instead 在函数的 func_code.co_code 属性中存储 MSIL [4]。这一事实使得 inspect.getargspec 函数在 IronPython 下运行时无法工作。目前尚不清楚其他 Python 实现是否受影响,但如果实现不是仅仅重新实现 Python 虚拟机,则合理地假设会受影响。

移除后不影响功能

内省问题中所述,为了处理元组参数,函数的字节码以将参数解包为正确参数名称所需的字节码开头。这意味着实现元组参数不需要特殊支持,因此如果将其移除,功能不会有任何损失,只是可能失去了便利性(这在为何(据称)应保留中有所阐述)。

本 PEP 开头的示例函数可以很容易地重写为:

def fxn(a, b_c, d):
    b, c = b_c
    pass

并且功能丝毫不会受损。

规则的例外

在查看 Python 函数可以拥有的各种参数类型时,会注意到元组参数往往是例外而不是规则。

考虑 PEP 3102(仅限关键字参数)和 PEP 3107(函数注解)。这两个 PEP 都已被接受,并在函数签名中引入了新功能。然而,这两个 PEP 的新特性都不能应用于整个元组参数。PEP 3102 完全不支持元组参数(这很有道理,因为没有办法通过名称引用元组参数)。PEP 3107 允许对元组中的每个项目进行注解(例如,(x:int, y:int)),但不能对整个元组进行注解(例如,(x, y):int)。

元组参数的存在还将序列对象与映射对象在函数签名中分离。无法将映射对象(例如,字典)作为参数传入,并使其以与序列解包到元组参数相同的方式解包。

无信息量的错误消息

考虑以下函数:

def fxn((a, b), (c, d)):
    pass

如果调用为 fxn(1, (2, 3)),则会收到错误消息 TypeError: unpack non-sequence。此错误消息完全没有告知哪个元组未正确解包。也没有任何迹象表明这是由于参数导致的结果。关于函数参数的其他错误消息明确说明了其与签名的关系:TypeError: fxn() takes exactly 2 arguments (0 given) 等。

使用率低

虽然我个人认识的少数 Python 程序员和 PyCon 2007 冲刺活动中的非正式调查表明,绝大多数人不知道这个特性,其余人只是不使用它,但需要一些硬数据来支持该特性使用不多的说法。

在 Python 代码仓库的 Lib/ 目录中遍历每一行,使用正则表达式 ^\s*def\s*\w+\s*\( 来检测函数和方法定义,在主干分支中发现了 22,252 个匹配项。

加上 .*,\s*\( 来查找包含元组参数的 def 语句,只发现了 41 个匹配项。这意味着在 def 语句中,只有 0.18% 的语句似乎使用了元组参数。

为何(据称)应保留

实际应用

在某些情况下,元组参数可能很有用。一个常见的例子是期望一个表示笛卡尔点的双项元组的代码。虽然能够为你解包 x 和 y 坐标确实很好,但论点是,这种微小的实际有用性被与元组参数相关的其他问题严重抵消了。正如移除后不影响功能中所示,它们的使用纯粹是实际的,并且无论如何都不能提供无法以其他方式轻松处理的独特功能。

参数的自文档化

有人认为,元组参数为预期为特定序列格式的参数提供了一种自文档化的方式。以我们从实际应用中引用的笛卡尔点示例为例,在函数中看到 (x, y) 作为参数,很明显该参数预期是一个长度为二的元组。

但是 Python 提供了其他几种方法来文档化参数的用途。文档字符串旨在提供足够的信息来解释预期的参数。元组参数可能会告诉你序列参数的预期长度,但它不会告诉你数据将用于什么。如果不是所有参数都是元组参数,还必须阅读文档字符串才能知道预期的其他参数。

函数注解(不适用于元组参数)也可以提供文档。因为注解可以是任何形式,所以以前的元组参数可以是一个带注解的单一参数,注解可以是 tupletuple(2)Cartesian point(x, y) 等。注解为文档化参数的预期用途提供了极大的灵活性,包括作为特定长度的序列。

过渡计划

为了将 Python 2.x 代码迁移到移除元组参数的 3.x,建议采取两个步骤。首先,当 Python 2.6 中的 Python 编译器遇到元组参数时,将发出适当的警告。这将像 Python 3.0 中相对于 Python 2.6 将发生的任何其他语法更改一样处理。

其次,2to3 重构工具 [1] 将获得一个 fixer [2],用于将元组参数转换为在函数中作为第一个语句解包的单个参数。新参数的名称将被更改。然后将新参数解包为最初在元组参数中使用的名称。这意味着以下函数:

def fxn((a, (b, c))):
    pass

将被转换为:

def fxn(a_b_c):
    (a, (b, c)) = a_b_c
    pass

由于 lambda 表达式因其单表达式限制而使用元组参数,因此也必须支持它们。这通过将预期的序列参数绑定到单个参数,然后对该参数进行索引来实现:

lambda (x, y): x + y

将被转换为:

lambda x_y: x_y[0] + x_y[1]

参考资料


来源: https://github.com/python/peps/blob/main/peps/pep-3113.rst

最后修改: 2025-02-01 08:59:27 GMT