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年1月17日, 2024年7月17日
- 决议:
- 2025年3月13日
摘要
本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 issue)。
几个现代语言在函数调用时提供了类似的功能,有时称为“punning”。例如
- 在 Ruby 中,
f(x:, y:)是f(x: x, y: y)的语法糖。请参阅 Ruby 3.1.0 release notes(搜索“keyword arguments”)。 - 在 ReasonML 中,
f(~x, ~y)是f(~x=x, ~y=y)的语法糖。请参阅 ReasonML function documentation(搜索“punning”)。 - 在 SystemVerilog 中,
(.mult, .mop1, .data);是(.mult(mult), .mop1(mop1), .data(data));的语法糖。请参阅 SystemVerilog Implicit Port Connections。 - 在 Jakt 中,
f(x, y)是f(x: x, y: y)的语法糖。请参阅 The Jakt programming language。
除了函数调用本身,还有更多语言提供类似的功能。
- 在 OCaml 中,
let+ x in …是let+ x = x in …的语法糖。请参阅 OCaml: Short notation for variable bindings (let-punning)。 - 在 JavaScript 中,
{ x, y }是{x: x, y: y}的语法糖。请参阅 JavaScript: Object Initializer。 - 在 Rust 中,
User { x, y }是User {x: x, y: y}的简写。
适用性
我们使用 此脚本 分析了近几年流行的 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 release notes(搜索“keyword arguments”)。
- 语法易于实现,因为它只是简单的语法糖。
- 与前缀形式(参见 Rejected Ideas)相比,此语法传达的是“这里有一个参数,去找到它的值”,这对于命名参数的语义来说更为合适。
- Python 开发者投票表明这是提议的语法中最受欢迎的一种。
如何教授此内容
为了方便沟通和搜索此功能,为该功能提供一个名称,例如“关键字参数简写”,也可能是有价值的。
热情的 Python 开发者很可能通过典型的信息渠道得知此功能,例如新闻版、社交媒体、邮件列表、在线论坛或口碑传播。更多的人会在阅读代码时遇到此功能,并注意到调用时关键字参数中缺少值,从而违背了他们的期望。我们应该确保这些开发者能够轻松获得解释此功能语义的文档,并且在搜索时易于找到此文档。例如,可以更新 Python Glossary 和 Tutorial,并使用合理的关键词来帮助搜索发现性。可以编写一个 StackOverflow 问题来帮助向那些搜索解释的人解释此功能。
老师可能会向新的 Python 程序员解释这个功能,例如,“当你看到一个参数后面只有一个等号时,比如 f(x=),这代表一个关键字参数,其中参数的名称和它的值是相同的。这等价于展开的写法 f(x=x)。” 根据学生的背景,老师可能会进一步将其与类似的其他语言的语法或 Python 的 f-string 语法 f"{x=}" 进行比较。
要理解这一点,Python 学生需要熟悉函数的基础知识以及现有的关键字参数语法。考虑到此功能是一个相对直接的语法糖,合理的情况是,掌握了关键字参数概念的学生能够快速吸收这个概念。f-string 语法以及其他语言中的类似功能(参见 Prior Art)的成功证明了这一点。
被拒绝的想法
已经提出了许多替代语法,但除了 f(=x) 或 f(x=) 之外,没有任何形式获得了显著的支持。我们在此列出一些最受欢迎的提议替代方案,以及为什么我们最终拒绝它们。
f(a, b, *, x)
有几次,人们曾提出借鉴关键字专用函数定义的语法。
赞成此提议
- 此语法因用于要求函数定义中的关键字专用参数而熟悉。
- Python 开发者投票表明这是提议语法中第二受欢迎的。
然而,我们反对:
- 对于任何给定的参数,从局部上下文很难确定它是位置参数还是命名参数。在长参数列表中,
*很容易被忽略,命名参数可能被读作位置参数,反之亦然。 - 不清楚未省略值的关键字参数是否可以跟随
*。如果是,那么它们的相对位置将是令人困惑的任意的,但如果不是,那么在不同类型的关键字参数之间就会强制执行任意分组,并且如果只更改一个名称(参数或其值),则需要重新排序参数。 - 在函数调用中使用
*已经确立,并且此提议将引入一个可能导致混淆的新效果。例如,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 建立。
- 程序员将像往常一样随着时间的推移进行调整。
该功能令人困惑
我们认为:
该功能不明确
我们认识到,显而易见地,参数值在此提议的语法中是“隐含的”。然而,我们认为这并不是《Python 之禅》所要阻止的。
从《Python 之禅》的意义上讲,关键字参数(例如)比位置参数更明确,因为省略了参数名,并且无法从局部上下文中得知。相反,整数的语法糖 x += 1 在此意义上并不比 x = x + 1 更隐含,尽管变量从右侧省略了,但因为从局部上下文中可以立即清楚它是什么。
本 PEP 提议的语法与 x += 1 的示例(尽管更简单,因为我们不提议引入新操作)非常相似。此外,通过消除现有关键字参数语法带来的视觉噪音障碍,此语法糖将鼓励使用关键字参数而不是位置参数,从而使典型的 Python 代码库总体上更加明确。
该功能增加了另一种做事方式
对于所有语法更改都可以做出相同的论证。这只是一个简单的语法糖,就像 x += 1 是 x = x + 1(当 x 是整数时)的糖一样。这并不是“一种新的”传递参数的方式,而是同一方式的一种更具可读性的符号。
在调用上下文中重命名变量将破坏代码
在绝大多数情况下,NameError 会使错误变得清晰。如果来自更广泛范围的变量与原始变量同名,可能会导致混淆,因此不会引发 NameError。然而,使用当前语法的关键字参数也可能出现此问题(尽管可以说,这种语法糖可能会使其更难发现)。此外,在不同作用域中具有相同名称的变量通常被认为是糟糕的做法,并且会被 linters 劝阻。
代码编辑器可以通过静态分析高亮显示问题——f(x=) 等同于编写 f(x=x)。如果 x 不存在,现代编辑器会轻松地高亮显示问题。
此语法增加了耦合
我们认识到,像往常一样,所有语法都有被误用的可能性,因此应谨慎使用以改进代码库。在这种情况下,如果参数及其值在两种上下文中具有相同的语义,这表明使用此语法是合适的,并且将有助于减轻损害可读性的无意不同步的风险。
然而,如果两个变量具有不同的语义,这表明不应使用此功能(因为它鼓励一致性),或者可能应该重命名其中一个或两个变量。
使用此语法的建议
与任何其他语言功能一样,程序员应自行判断在任何给定上下文中是否明智使用它。我们不建议强制执行在所有可能适用的情况下使用该功能的规则,例如通过 lint 规则或样式指南。
正如在 This syntax increases coupling 中所述,我们建议一个经验法则是在参数和其参数具有相同语义的情况下使用此功能,以减少无意不同步,同时避免不当耦合。
对编辑的影响
使用纯文本编辑器
使用纯文本编辑器进行编辑通常不受影响。
当使用“查找-替换”方法重命名变量时,在使用此语法的地方,开发人员会在调用时遇到函数参数(就像未使用此语法时一样)。此时,他们可以像平常一样决定是更新参数还是展开为完整的 f(x=x) 语法。
与当前语法一样,“查找-替换全部”方法将失败,因为在绝大多数情况下,关键字参数在函数定义时不存在。
如果开发人员不更改参数名称并忘记更新其值,则通常会引发 NameError,如 Renaming the variable in the calling context will break the code 中所述。
IDE的建议
为响应社区反馈,我们在此提供有关 IDE 如何处理此语法的建议。然而,我们将其留给开发 IDE 的领域专家自行决定。
通过认识到 f(x=) 只是 f(x=x) 的语法糖,并且应与当前情况相同地处理,大多数考虑因素都变得简单了。
高亮显示NameErrors
IDE 通常提供高亮显示可能导致 NameError 的代码的功能。我们建议将此语法与展开形式 f(x=x) 类似地对待,以识别和高亮显示省略的变量可能不存在的情况。高亮显示这些情况可能使用的视觉线索可能与当前语法相同或不同,具体取决于 IDE。
跳转到定义
根据鼠标悬停/光标位置,有几种方法可以实现“跳转到定义”功能。
一种选择是:
- 如果鼠标悬停/光标位于参数上,则跳转到函数定义中的参数
- 如果鼠标悬停/光标位于我们提议的语法中
=之后的字符上,则跳转到省略变量的定义
另一个潜在的补充选项是,在鼠标悬停时以视觉方式展开语法,并启用 Ctrl+Click(或 Cmd+Click)跳转到变量的定义。
高亮显示其他引用
IDE 经常高亮显示与当前鼠标悬停/光标位置的值匹配的代码引用。使用这种简写语法,当鼠标悬停/光标位于参数名上时,可能有价值的是:
- 高亮显示对参数及其值的引用,反映出该名称现在同时引用两者
- 在鼠标悬停时以视觉方式展开语法(如上),并根据光标应用已建立的突出显示逻辑
重命名符号
IDE 可以通过几种方式支持此语法的“重命名符号”功能。例如,如果正在重命名参数,IDE 可能会:
- 在为此语法使用此语法的每个调用上下文中,也重命名用作其值的变量
- 展开以使用完整语法,将未更改的变量作为重命名参数的值传递
- 提示开发人员在上述两个选项之间进行选择
最后一种选择似乎最可取,可以减少名称的无意不同步,同时向程序员突出显示更改。
参考实现
由 @Hels15 为 CPython 提供了一个 提议的实现。我们将扩展此实现,添加一个 AST 节点属性来指示关键字的值是否被省略。否则,AST 将保持不变。
参考资料
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0736.rst