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

Python 增强提案

PEP 640 – 未使用的变量语法

作者:
Thomas Wouters <thomas at python.org>
状态:
已拒绝
类型:
标准跟踪
创建日期:
2020年10月4日
Python 版本:
3.10
发布历史:
2020年10月19日
决议:
Python-Dev 消息

目录

拒绝说明

已被指导委员会拒绝:https://mail.python.org/archives/list/python-dev@python.org/message/SQC2FTLFV5A7DV7RCEAR2I2IKJKGK7W3/

摘要

此 PEP 提出了一种新的语法来表示未使用的变量,提供一个可以赋值但不能以其他方式使用的伪名称。赋值实际上不会发生,而是将值丢弃。

动机

在 Python 中,需要进行赋值但不实际需要结果的情况相当普遍。传统上,人们会使用 "_" 或诸如 "unused"(或以 "unused" 为前缀)之类的名称来表示。这在解包赋值中最常见。

x, unused, z = range(3)
x, *unused, z = range(10)

for 循环和列表推导式中也会用到。

for unused in range(10): ...
[ SpamObject() for unused in range(10) ]

在这些情况下使用 "_" 可能是最常见的,但它可能与国际化中 "_" 的使用相冲突,因为在国际化中,像 gettext.gettext() 这样的调用会被绑定到 "_" 并用于标记需要翻译的字符串。

在向 Python 添加模式匹配的提案中(最初是PEP 622,现在已拆分为PEP 634PEP 635PEP 636),"_"额外的特殊含义。它是一个通配符模式,用于可以赋值变量的地方,表示匹配任何内容但不赋值给任何东西。选择 "_" 在这里与其他语言中 "_" 的用法一致,但与 Python 中其他地方的 "_" 相比,其语义差异是显著的。

此 PEP 提议允许使用一个特殊的标记 "?" 来代替任何有效的变量名进行赋值。这具有 "_" 的大部分优点,同时不会影响该变量的其他用途。允许使用相同的通配符模式将使模式匹配和解包赋值彼此更加一致。

基本原理

将某些变量标记为未使用是一个有用的工具,因为它可以提高代码的清晰度。它向代码的读者和自动化 linter 表明,某个特定变量是有意未使用的。

然而,尽管有约定,"_" 并不是一个特殊变量。值仍然被赋值,它所引用的对象仍然被保留在作用域结束之前,并且仍然可以使用。同样,在未使用变量时使用 "_" 并不是普遍的,因为它与传统的国际化冲突,并不明显它是一个普通变量,而且不像名为 "unused" 的变量那样明显是未使用的。

在模式匹配提案中,使用 "_" 作为通配符模式,通过将其置于单独的作用域中,绕过了 "_" 用于未使用变量的问题。它与国际化唯一的冲突是潜在的混淆,它实际上不会与全局变量 "_" 的使用发生交互。然而,为这个通配符模式特殊处理 "_" 仍然存在问题:在模式匹配内部和外部 "_" 的不同语义和含义意味着 Python 的一致性被打破。

在模式匹配内部和外部引入 "?" 作为未使用变量的特殊语法,使我们能够保持这种一致性。它避免了与国际化或 _ 作为变量的其他任何用途的冲突。它使解包赋值更接近模式匹配,从而更容易将模式匹配解释为解包赋值的扩展。

在代码可读性方面,使用特殊标记更容易了解其含义("Python 中的 问号 有什么作用" 而不是 "为什么我的 _ 变量没有被赋值"),并且更清楚地表明实际意图是值未被使用——因为完全不可能使用它。

规范

引入了一个新的标记 "?",即 token.QMARK

语法被修改为允许在赋值上下文(当前语法中的 star_atomt_atom)中使用 "?",创建一个标识符设置为 NULL 的 Name AST 节点。

AST 被修改为允许 Name 表达式的标识符是可选的(目前是必需的)。标识符为空只允许在 STORE 上下文中。

在 CPython 中,字节码编译器被修改为发出 POP_TOP 而不是 STORE_NAME,用于标识符为空的 Name 节点。 Name 节点的其他用法也已更新,以在适当的情况下处理标识符为空的情况。

修改后的语法节点的用途至少包括以下形式的赋值:

? = ...
x, ?, z = ...
x, *?, z = ...
for ? in range(3): ...  # including comprehension forms
for x, ?, z in matrix: ...  # including comprehension forms
with open(f) as ?: ...
with func() as (x, ?, z): ...

在非解包上下文中,单独使用 "?" 被允许用于普通赋值和 with 语句。它本身意义不大,并且可以禁止这些特定情况。但是,for ? in range(3) 显然有其用途,因此出于一致性考虑(如果仅此而已),在其他情况下也允许使用单个 "?" 似乎更合理。

不允许在增强赋值(? *= 2)中使用 "?",因为 "?" 只能用于赋值。与赋值给变量名一样,多个 "?" 的出现是有效的,并且赋值之间不会相互干扰。

向后兼容性

引入新标记意味着没有向后兼容性问题。没有有效的语法会改变含义。

"?" 不被视为标识符,因此 str.isidentifier() 不会改变。

AST 的更改方式不兼容,因为 Name 标记的标识符现在可以是空的。使用 AST 的代码将不得不相应地进行调整。

如何教授此内容

可以与解包赋值一起引入 "?",解释它是“未使用”的特殊语法,并提及它也可以在其他地方使用。或者,可以将其作为对 for 循环中赋值的解释的一部分来引入,展示一个循环变量未使用的示例。

PEP 636 讨论了如何教授 "_",并且可以简单地将 "_" 替换为 "?",也许会指出 "?" 在其他上下文中也是类似可用的。

参考实现

原型实现存在于 <https://github.com/Yhg1s/cpython/tree/nonassign>。

被拒绝的想法

未解决的问题

是否应该在以下上下文中允许 "?"

# imports done for side-effect only.
import os as ?
from os import path as ?

# Function defined for side-effects only (e.g. decorators)
@register_my_func
def ?(...): ...

# Class defined for side-effects only (e.g. decorators, __init_subclass__)
class ?(...): ...

# Parameters defined for unused positional-only arguments:
def f(a, ?, ?): ...
lambda a, ?, ?: ...

# Unused variables with type annotations:
?: int = f()

# Exception handling:
try: ...
except Exception as ?: ...

# With blocks:
with open(f) as ?: ...

从一致性角度来看,其中一些可能看起来有意义,但实际用途有限且可疑。对 "?" 的类型注解以及与 exceptwith 的使用似乎都没有意义。在参考实现中,except 不受支持(现有语法只允许名称),但 with 是受支持的(因为现有语法支持解包赋值)。

即使模式匹配被拒绝,此 PEP 是否仍然应该被接受?


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

最后修改:2025-02-01 08:55:40 GMT