Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

PEP 736 – 关键字参数调用简写语法

作者:
Joshua Bambrick <jbambrick at google.com>, Chris Angelico <rosuav at gmail.com>
讨论对象:
Discourse 线程
状态:
草案
类型:
标准轨道
创建:
2023-11-28
Python 版本:
3.14
历史记录:
2023-10-14, 2024-01-17

目录

摘要

本 PEP 提案引入语法糖 f(x=) 用于将命名参数与对应值变量名相同的模式 f(x=x) 简化。

动机

关键字参数语法可能变得不必要地重复和冗长。

请考虑以下调用

my_function(
  my_first_variable=my_first_variable,
  my_second_variable=my_second_variable,
  my_third_variable=my_third_variable,
)

关键字参数名称与值变量名匹配的情况在 Python 库中很常见。这种冗长和重复性会阻碍使用命名参数,并通过增加视觉噪声来降低可读性。

基本原理

有两种方法可以调用带有参数的函数:按位置和按关键字。关键字参数通过显式性提供了许多优势,从而提高了可读性并最大程度地降低了意外换位的风险。另一方面,位置参数通常用于简化冗余和视觉噪声。

我们认为,一种简单的语法糖可以简化这种常见模式,并带来许多好处

鼓励使用命名参数

这种语法将鼓励使用命名参数,从而提高可读性并减少参数换位造成的错误。

减少冗余

通过最小化视觉噪声,在某些情况下甚至减少代码行数,我们可以提高可读性。

鼓励一致的变量名

一个常见的问题是,语义上相同的变量在不同的上下文中具有不同的名称。这种语法将鼓励作者在调用函数时使用与参数名相同的变量名,这将提高使用的变量名的一致性,从而也提高可读性。

突出显示不遵循此模式的参数

使用当前语法,如果许多参数从本地上下文转发,则由于视觉噪声,其他参数值可能很容易被忽略。例如

add_middleware(
    excluded_urls=excluded_urls,
    server_request=server_request,
    client_request=client_request,
    client_response=client_response,
    span_details=_get_span_details(),
    tracer=tracer,
    meter=meter,
)

使用这种语法,异常参数更容易识别

add_middleware(
    excluded_urls=,
    server_request=,
    client_request=,
    client_response=,
    span_details=_get_span_details(),
    tracer=,
    meter=,
)

字典构造的适用性

这种语法可以应用于字典构造,其中经常出现类似的模式(字典键与分配为其值的变量名称相同),{"x": x, "y": y}dict(x=x, y=y)。有了这个功能,现在可以很容易地写成 dict(x=, y=)。是否在字典文字中进一步支持类似的语法是一个超出本 PEP 范围的开放性问题。

规范

我们建议引入语法糖,以便如果函数调用中省略了关键字参数的值,则该参数的值将被推断为与调用范围内的该名称匹配的变量。

例如,函数调用

my_function(my_first_variable=, my_second_variable=, my_third_variable=)

将在语义上等效于现有语法中的以下内容

my_function(
  my_first_variable=my_first_variable,
  my_second_variable=my_second_variable,
  my_third_variable=my_third_variable,
)

如果调用范围中没有变量与该名称匹配,则会引发 NameError,方式与使用已建立的扩展语法相同。

本提案仅涉及函数调用;函数定义不受语法更改的影响。所有现有的有效语法保持不变。

向后兼容性

仅添加了以前在语法上错误的语法。没有修改现有的有效语法。因此,建议的更改完全向后兼容。

安全影响

此更改没有任何安全隐患。

现有技术

Python 已经在 f-string 插值中具备了非常类似的功能,其中 f'{x=}' 实际上扩展为 f'x={x}'(见 相关 GitHub 问题)。

几种现代语言在函数调用期间提供了类似的功能,有时称为“双关语”。例如

  • 在 Ruby 中,f(x:, y:)f(x: x, y: y) 的语法糖。参见 Ruby 3.1.0 发行说明(搜索“关键字参数”)。
  • 在 ReasonML 中,f(~x, ~y)f(~x=x, ~y=y) 的语法糖。参见 ReasonML 函数文档(搜索“双关语”)。
  • 在 SystemVerilog 中,(.mult, .mop1, .data);(.mult(mult), .mop1(mop1),  .data(data)); 的语法糖。参见 SystemVerilog 隐式端口连接.
  • 在 Jakt 中,f(x, y)f(x: x, y: y) 的语法糖。参见 Jakt 编程语言.

除了函数调用之外,更多语言提供了类似的功能

适用性

我们使用 此脚本 分析了近年来流行的 Python 库,以计算

  • 调用时 f(x=x) 形式的关键字参数数量。
  • 调用时 f(x=x) 形式的关键字参数所占的百分比。
  • 使用这种语法糖来减少换行需求可以节省的代码行数。

本练习的目的是计算这种模式的流行程度的统计数据,不应将其解释为建议普遍应用所提出的语法糖。

统计数据 polars fastapi rich httpx
调用时 f(x=x) 形式的关键字参数数量 1,654 1,408 566 759
调用时 f(x=x) 形式的关键字参数所占的百分比 15.83% 28.11% 15.74% 45.13%
节省的行数 170 35 62 117

据此,我们注意到 f(x=x) 关键字参数模式很普遍,根据代码库的不同,它占所有关键字参数使用的 15% 到不到一半。

建议的语法

虽然这个功能已经被多次提出,并且有几种不同的形式 [1] [2] [3] [4] [5], [6],但我们选择倡导使用 f(x=) 形式,原因如下

  • 这个功能在十年内被频繁地提出,其中 f(x=)f(=x) 是迄今为止最常见的语法 [1] [2] [6]。这是一个强烈的迹象,表明它是明显的符号。
  • 所提出的语法与 f-string 调试 f'{var=}' 语法(已建立的 Pythonic 风格)非常相似,并用于几乎相同的目的。
  • 所提出的语法与 Ruby 关键字参数语法糖完全相同。参见 Ruby 3.1.0 发行说明(搜索“关键字参数”)。
  • 该语法易于实现,因为它只是简单的语法糖。
  • 与前缀形式相比(见 被拒绝的想法),这种语法传达的是“这里有一个参数,去寻找它的参数”,这更适合命名参数的语义。
  • 一项针对 Python 开发者的调查显示,这是在所有提议的语法中,最受欢迎的一种。

如何教授

为了方便交流和搜索此功能,为其提供一个名称,例如“关键字参数简写”,可能也很有价值。

热心的 Python 开发者可能会通过典型的信息渠道(例如新闻板、社交媒体、邮件列表、在线论坛或口碑传播)了解到此功能。 更多的人会在阅读代码时遇到此功能,并注意到在调用时关键字参数中省略了值,违反了他们的预期。 我们应该确保这些开发者可以轻松访问解释此功能语义的文档,并且这些文档在搜索时易于找到。 例如,可以相应地更新Python 词汇表教程,并使用合理的关键字来帮助搜索发现。 一个 StackOverflow 问题可以用来帮助解释此功能,以供那些正在寻找解释的人。

老师可以向新的 Python 程序员解释这个功能, “当你看到一个参数后面跟着一个等号,例如 f(x=),它表示一个关键字参数,其中参数的名称和值相同。 这可以用扩展的符号等效地写为 f(x=x)。” 根据学生的背景,老师可能会将其进一步与其他语言中的等效语法或 Python 的 f-string 语法 f"{x=}"进行比较。

要理解这一点,Python 的学生需要熟悉函数的基础知识以及现有的关键字参数语法。 鉴于此功能是一个相对简单的语法糖,一个掌握了关键字参数的学生能够快速掌握这个概念是合理的。 这一点也证明了 f-string 语法以及其他语言中类似功能的成功(参见先验艺术)。

被拒绝的想法

已经提出了许多替代语法,但除了 f(=x)f(x=) 之外的语法都没有得到很大程度的支持。 我们在这里列举一些最受欢迎的提议的替代方案,以及我们最终拒绝它们的原因。

f(a, b, *, x)

在一些场合,人们提出过借用关键字参数函数定义的语法。

支持这个提议

然而,我们反对

  • 对于任何给定的参数,从本地上下文不清楚它是位置参数还是命名参数。 在一个很长的参数列表中,很容易错过 *,并且命名参数可能被误读为位置参数,反之亦然。
  • 尚不清楚是否允许省略值的关键字参数跟随 *。 如果是,那么它们之间的相对位置将令人困惑地随意,但如果不是,那么不同类型关键字参数之间会强制执行一个任意的分组,并且如果只更改了一个名称(参数或其值),则需要重新排序参数。
  • 在函数调用中使用 * 已经确立,这个提议会引入一个新的效果,这可能会造成混淆。 例如, f(a, *x, y) 的含义将不同于 f(a, *, x, y)

f(=x)

支持这种形式

  • 前缀运算符更类似于函数调用中已有的 *args**kwargs 语法。
  • 当参数垂直排列时,它会更多地引起人们的注意。 特别是,如果参数长度不同,则更难在末尾找到等号。 此外,由于 Python 从左到右读取,因此使用此功能对于读者来说更清晰。

相反

  • 虽然前缀版本在视觉上更明显,但在实践中,此功能没有必要像典型命名参数那样大声地宣告自己的存在。 当我们读到 = 时,很明显值会自动填充,就像在典型的关键字参数情况下一样。
  • 语义上,这种形式传达了“这里有一个值,填入参数”,这不是我们想表达的意思。
  • 它与 f-string 语法不太相似。
  • 不太明显的是,任意的表达式是无效的,例如 f(=a + b)

f(%x)f(:x)f(.x)

已经提出了几种这种语法的变体,其中前缀形式用其他字符替换了 =。 然而,没有一种形式获得了足够的关注,并且与 = 相比,符号的选择显得任意。 此外,在现有的语言特性(例如 f-string)或其他语言(例如 Ruby)方面,这种做法没有先例。

反对意见

引入这种语法糖只有几个强烈的反对意见。 大多数不支持此功能的人都是“我不会使用它”的阵营。 然而,在关于此功能的广泛讨论中,以下反对意见是最常见的

语法很丑

这个反对意见是最常见的。 相反,我们认为

  • 这个反对意见是主观的,许多社区成员不认同。
  • 一个几乎相同的语法已经为 f-string 确立。
  • 程序员将像往常一样,随着时间的推移而适应。

该特性令人困惑

我们认为

  • 引入新功能通常会暂时产生这种影响。
  • 语法与已有的 f'{x=}' 语法非常相似。
  • 此功能和语法在其他流行的现代语言中很常见。
  • x= 扩展为 x=x 事实上是一个微不足道的功能,从本质上来说,它比 *arg**kwarg 扩展要简单得多。
  • 这种特殊的语法形式已经多次被独立提出,表明它是最显而易见的[1] [2] [6]

该特性不够明确

我们认识到,从明显的意义上说,参数值在这个提议的语法中是“隐式”的。 然而,我们并不认为这就是 Python 之禅想要阻止的。

从我们认为 Python 之禅所指的意义上说,关键字参数(例如)比省略了参数名称且无法从本地上下文判断其名称的位置参数更明确。 相反,整数的语法糖 x += 1 在这种意义上并不比 x = x + 1 更隐式,即使变量在右侧被省略了,因为从本地上下文立即清楚它是什么。

本 PEP 中提议的语法与 x += 1 示例更为类似(尽管更简单,因为我们没有提议引入新的操作)。 此外,引入这种语法糖应该鼓励使用关键字参数而不是位置参数,从而使典型的 Python 代码库在总体上更明确。

该特性增加了另一种做事方式

同样的论点可以用来反对所有语法更改。 这只是一个简单的语法糖,就像当 x 是一个整数时, x += 1x = x + 1 的糖一样。 这并不等同于传递参数的“新方式”,而是一种更可读的符号,用于表示相同的方式。

在调用上下文中重命名变量将破坏代码

在大多数情况下,一个 NameError 会使错误很明显。 如果一个来自更广泛范围内的变量与原始变量具有相同的名称,可能会造成混淆,因此不会引发 NameError。 然而,这个问题也可能发生在使用当前语法的关键字参数中(可以说,这种语法糖可能会使它更难发现)。 此外,在不同的范围内拥有具有相同名称的变量通常被认为是不好的做法,并且被代码检查器阻止。

代码编辑器可以根据静态分析来突出显示问题 - f(x=) 等效于编写 f(x=x)。 如果 x 不存在,现代编辑器可以轻松地突出显示该问题。

这种语法增加了耦合

我们认识到,像往常一样,所有语法都有被误用的可能性,因此应该谨慎使用它们来改进代码库。 在这种情况下,如果一个参数及其值在两种情况下都具有相同的语义,那么这可能表明使用这种新语法是合适的,并且将有助于改善无意中不同步的风险,从而损害可读性。

但是,如果两个变量具有不同的语义,那么这可能表明不应该使用此功能来鼓励一致性,甚至应该对它们进行重命名。

使用该语法的建议

与任何其他语言特性一样,程序员应该自行判断在任何给定情况下使用它是明智的。 我们不建议在所有可能适用此功能的情况下强制执行一个规则。

上面所述,我们建议一个合理的经验法则是,在参数和参数在语义上相同的情况下使用此功能,以减少无意中不同步的风险,而不会造成不适当的耦合。

对编辑的影响

使用纯文本编辑器

使用纯文本编辑器进行编辑通常不会受到影响。

在使用“查找-替换”方法重命名变量时,如果使用了这种语法,开发人员会在调用时遇到函数参数(就像没有使用这种语法时一样)。 此时,他们可以像往常一样决定是否也更新参数,或者扩展到完整的 f(x=x) 语法。

与当前语法类似,‘全部查找替换’方法会失败,因为在大多数情况下,函数定义时关键字参数不存在。

如果开发人员保留参数名称不变并忘记更新其值,通常会引发一个 NameError,如 上述 所述。

IDE 的建议

为了回应社区反馈,我们提供了一些关于 IDE 如何处理这种语法的建议。但是,我们当然尊重开发 IDE 的领域专家使用他们自己的判断。

大多数考虑因素都变得简单,因为认识到 f(x=) 只是 f(x=x) 的语法糖,应该像现在一样对待。

突出显示 NameErrors

IDE 通常提供一个功能来突出显示可能导致 NameError 的代码。我们建议将这种语法与扩展形式 f(x=x) 类似地对待,以识别和突出显示省略值变量可能不存在的情况。根据 IDE,用于突出显示这些情况的视觉提示可能与当前语法相同或不同。

跳转到定义

根据插入符号/光标位置,‘跳转到定义’功能可以实现几种可能的方式。

一个选项是

  • 如果插入符号/光标位于参数上,则跳转到函数定义中的参数
  • 如果插入符号/光标位于我们建议语法中 = 后的字符上,则跳转到省略变量的定义

另一个可能是互补的选项是在鼠标悬停时可视地扩展语法,并启用 Ctrl+Click(或 Cmd+Click)跳转到变量的定义。

突出显示其他引用

IDE 经常突出显示与当前插入符号/光标位置的值匹配的代码引用。使用这种简写语法,当插入符号/光标位于参数名称上时,以下两种方法可能会有价值:

  • 突出显示参数及其值的两个引用,反映这个名称现在指的是两者
  • 在鼠标悬停时可视地扩展语法(如上所述),并根据光标应用已建立的突出显示逻辑

重命名符号

IDE 可以通过几种方式支持这种语法下的‘重命名符号’功能。例如,如果要重命名参数,IDE 可以

  • 在使用此语法的每个调用上下文中,也重命名用作其值的变量
  • 扩展为使用完整语法传递用作其值的变量
  • 提示开发人员在上述两个选项中选择

最后一个选项似乎最可取,因为它可以减少无意间名称不同步,同时提醒用户注意更改。

参考实现

提议的实现 针对 cpython 由 @Hels15 提供。我们将扩展此实现,添加一个 AST 节点属性来指示关键字的值是否被省略。

参考文献


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

最后修改时间: 2024-06-22 18:09:07 GMT