PEP 640 – 未使用变量语法
- 作者:
- Thomas Wouters <thomas at python.org>
- 状态:
- 拒绝
- 类型:
- 标准跟踪
- 创建:
- 2020-10-04
- 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 建议允许使用一个特殊标记 "?"
来代替赋值中的任何有效名称。它具有 "_"
的大多数优点,而不影响该常规变量的其他用途。允许使用相同的通配符模式将使模式匹配和解包赋值更加一致。
理由
将某些变量标记为未使用是一个有用的工具,因为它有助于提高代码的目的性。它使代码的读者和自动代码整理器清楚地知道某个特定变量是 _故意_ 未使用的。
然而,尽管有这种惯例,"_"
并不是一个特殊的变量。该值仍然会被分配,它引用的对象仍然会保留到范围结束,并且它仍然可以使用。使用 "_"
来表示未使用变量也并非完全无处不在,因为它与传统的国际化冲突,它并不明显这是一个常规变量,并且它不像名为 "unused"
的变量那样明显地未使用。
在模式匹配提案中,由于它在单独的范围内,使用 "_"
作为通配符模式避免了使用 "_"
表示未使用变量的问题。它与国际化的唯一冲突是潜在的混淆,它不会真正与使用名为 "_"
的全局变量交互。然而,将 "_"
特殊用于此通配符模式目的仍然是有问题的:"_"
在模式匹配内部和外部的不同语义 _和含义_ 意味着 Python 的一致性出现了断裂。
引入 "?"
作为 _在模式匹配内部和外部_ 表示未使用变量的特殊语法,可以让我们保持这种一致性。它避免了与国际化 _或任何其他使用 _ 作为变量的用途_ 发生冲突。它使解包赋值与模式匹配更加一致,使模式匹配更容易被解释为解包赋值的扩展。
在代码可读性方面,使用特殊标记可以更容易地找出它的含义("what does question mark in Python do"
与 "why is my _ variable not getting assigned to"
),并使实际意图更加明显,即该值不应该使用 - 因为它完全不可能使用。
规范
引入了一个新标记 "?"
或 token.QMARK
。
语法修改为允许在赋值上下文中使用 "?"
(在当前语法中为 star_atom
和 t_atom
),创建一个标识符设置为 NULL 的 Name
AST 节点。
AST 修改为允许 Name
表达式的标识符可选(目前是必需的)。标识符为空只允许在 STORE
上下文中使用。
在 CPython 中,字节码编译器被修改为为没有标识符的 Name
节点发出 POP_TOP
,而不是 STORE_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
最后修改: 2023-09-09 17:39:29 GMT