PEP 640 – 未使用的变量语法
- 作者:
- Thomas Wouters <thomas at python.org>
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2020年10月4日
- Python 版本:
- 3.10
- 发布历史:
- 2020年10月19日
- 决议:
- Python-Dev 消息
拒绝说明
摘要
此 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 634、PEP 635 和PEP 636),"_" 有额外的特殊含义。它是一个通配符模式,用于可以赋值变量的地方,表示匹配任何内容但不赋值给任何东西。选择 "_" 在这里与其他语言中 "_" 的用法一致,但与 Python 中其他地方的 "_" 相比,其语义差异是显著的。
此 PEP 提议允许使用一个特殊的标记 "?" 来代替任何有效的变量名进行赋值。这具有 "_" 的大部分优点,同时不会影响该变量的其他用途。允许使用相同的通配符模式将使模式匹配和解包赋值彼此更加一致。
基本原理
将某些变量标记为未使用是一个有用的工具,因为它可以提高代码的清晰度。它向代码的读者和自动化 linter 表明,某个特定变量是有意未使用的。
然而,尽管有约定,"_" 并不是一个特殊变量。值仍然被赋值,它所引用的对象仍然被保留在作用域结束之前,并且仍然可以使用。同样,在未使用变量时使用 "_" 并不是普遍的,因为它与传统的国际化冲突,并不明显它是一个普通变量,而且不像名为 "unused" 的变量那样明显是未使用的。
在模式匹配提案中,使用 "_" 作为通配符模式,通过将其置于单独的作用域中,绕过了 "_" 用于未使用变量的问题。它与国际化唯一的冲突是潜在的混淆,它实际上不会与全局变量 "_" 的使用发生交互。然而,为这个通配符模式特殊处理 "_" 仍然存在问题:在模式匹配内部和外部 "_" 的不同语义和含义意味着 Python 的一致性被打破。
在模式匹配内部和外部引入 "?" 作为未使用变量的特殊语法,使我们能够保持这种一致性。它避免了与国际化或 _ 作为变量的其他任何用途的冲突。它使解包赋值更接近模式匹配,从而更容易将模式匹配解释为解包赋值的扩展。
在代码可读性方面,使用特殊标记更容易了解其含义("Python 中的 问号 有什么作用" 而不是 "为什么我的 _ 变量没有被赋值"),并且更清楚地表明实际意图是值未被使用——因为完全不可能使用它。
规范
引入了一个新的标记 "?",即 token.QMARK。
语法被修改为允许在赋值上下文(当前语法中的 star_atom 和 t_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 讨论了如何教授 "_",并且可以简单地将 "_" 替换为 "?",也许会指出 "?" 在其他上下文中也是类似可用的。
参考实现
被拒绝的想法
未解决的问题
是否应该在以下上下文中允许 "?":
# 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 ?: ...
从一致性角度来看,其中一些可能看起来有意义,但实际用途有限且可疑。对 "?" 的类型注解以及与 except 和 with 的使用似乎都没有意义。在参考实现中,except 不受支持(现有语法只允许名称),但 with 是受支持的(因为现有语法支持解包赋值)。
即使模式匹配被拒绝,此 PEP 是否仍然应该被接受?
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0640.rst