PEP 679 – 带有括号的新 assert 语句语法
- 作者:
- Pablo Galindo Salgado <pablogsal at python.org>, Stan Ulbrych <stanulbrych at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 创建日期:
- 2022年1月7日
- Python 版本:
- 3.15
- 发布历史:
- 2025年9月8日, 2022年1月10日
摘要
本 PEP 提议允许在 assert 的双参数形式中使用括号。解释器会将 assert (expr, msg) 重新解释为 assert expr, msg,从而消除常见的陷阱,即此类代码之前被视为断言一个包含两个元素的 tuple,而该元组总是为真。
动机
当使用包含错误消息的 assert 语句形式时,用户常犯的一个错误是用括号将其括起来 [1] [2]。这是因为许多初学者认为 assert 是一个函数。著名的 unittest 方法,特别是 assertTrue(),也要求断言和消息周围有括号。
不幸的是,这个错误没有被检测到,因为 assert 总是通过 [6],因为它被解释为一个 assert 语句,其中表达式是一个两元组,它总是具有真值。当将测试或描述扩展到单行之外时,也经常发生这种错误,因为括号是实现这一目标的自然方式。
这种情况非常普遍,以至于自 3.10 版本以来,编译器 会发出一个 SyntaxWarning,并且一些代码检查工具也会发出警告 [3] [4]。
此外,语言中的其他一些语句也允许带括号的形式,例如 import 语句 (from x import (a,b,c)) 或 del 语句 (del (a,b,c))。
允许使用括号不仅能消除陷阱,还能让用户和自动格式化工具以本文作者认为更自然的方式将长断言语句格式化为多行。尽管目前可以使用反斜杠(如 PEP 8 所建议)或括号和逗号将长 assert 语句格式化为多行。
assert (
very very long
test
), (
"very very long "
"error message"
)
本文作者认为,所提议的带括号形式更清晰直观,也与其他语法结构的格式更一致。
assert (
very very long
test,
"very very long "
"message"
)
基本原理
由于向后兼容性问题(参见下文),为了告知用户之前作为两元素元组解析的内容现在解析方式发生变化,将引发一个 SyntaxWarning,并带有类似 "new assertion syntax, will assert first element of tuple" 的消息,直到 Python 3.17。例如,当使用新语法时:
>>> assert ('Petr' == 'Pablo', "That doesn't look right!")
<python-input-0>:0: SyntaxWarning: new assertion syntax, will assert first element of tuple
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
assert ('Petr' == 'Pablo', "That doesn't look right!")
^^^^^^^^^^^^^^^^^
AssertionError: That doesn't look right!
请注意,改进一般语法警告超出了本 PEP 的范围。
规范
| 'assert' '(' expression ',' expression [','] ')' &(NEWLINE | ';')
| 'assert' a=expression [',' expression ]
其中第一行是允许使用括号的 assert 语句的新形式,在 3.17 之前将引发 SyntaxWarning。需要先行查找以防止解析器急于将元组捕获为完整语句,因此像 assert (a, b) <= c, "something" 这样的语句仍然可以正确解析。
实现说明
此更改可以在解析器或编译器中实现。关于应引发 SyntaxWarning 以告知用户新语法的规范使得实现复杂化,因为警告应在编译期间引发。
作者认为,理想的实现应该在解析器中 [8],从而使 assert (x,y) 具有与 assert x,y 相同的 AST。这需要一个两步实施计划,并有一个必要的临时折衷方案。
在解析器中实现
不可能有纯解析器实现和警告规范。(请注意,如果没有警告规范,纯解析器实现只是一个小小的语法更改 [5])。为了引发警告,编译器必须知道新语法,这意味着需要一个可选标志,否则信息将在解析过程中丢失。因此,带括号的 assert 的 AST 将如下所示,带有一个 paren_syntax=1 标志。
>>> print(ast.dump(ast.parse('assert(True, "Error message")'), indent=4))
Module(
body=[
Assert(
test=Constant(value=True),
msg=Constant(value='Error message'),
paren_syntax=1)])
在编译器中实现
新的语法可以通过特殊处理长度为二的元组在编译器中实现。然而,这将产生一个副作用,即在 SyntaxWarning 发出期间的过渡时期,AST 不会发生任何修改。
一旦 SyntaxWarning 被移除,实现可以移到解析器级别,其中带括号的形式将直接解析成与 assert expression, message 相同的 AST 结构。这种方法更向后兼容,因为许多处理 AST 的工具将有更多时间进行适应。
向后兼容性
这项改变在技术上与旧版本不兼容。无论最初是在解析器还是编译器中实现,assert (x,y),目前被解释为一个以 2 元组为主题的 assert 语句并且总是为真,将被解释为 assert x,y。
另一方面,此类断言语句总是通过,因此它们在用户代码中实际上什么也没做。本文作者认为这种向后不兼容性是有益的,因为它将突出用户代码中的这些情况,而之前这些情况可能未被注意到。自 Python 3.10 以来,这种情况已经引发了 SyntaxWarning,因此已经有超过 5 年的废弃期。持续引发 SyntaxWarning 应该可以减轻意外情况。
此更改还将导致 assert (x,y) 的 AST 发生变化,目前是
Module(
body=[
Assert(
test=Tuple(
elts=[
Name(id='x', ctx=Load()),
Name(id='y', ctx=Load())],
ctx=Load()))],
type_ignores=[])
最终实现在 Python 3.18 中将产生以下 AST
Module(
body=[
Assert(
test=Name(id='x', ctx=Load()),
msg=Name(id='y', ctx=Load()))],
type_ignores=[])
问题在于,第一种形式的 AST 在技术上是“不正确的”,因为我们已经有了带有测试和消息的 assert 语句的 AST 的专用形式(第二种)。最初在编译器中实现将延迟这一更改,从而减轻向后兼容性问题,因为工具将有更多时间进行调整。
如何教授此内容
assert 语句的新形式将作为语言标准的一部分进行文档化。
在向用户教授带有错误消息的 assert 语句形式时,现在可以注意到添加括号也按预期工作,这允许将语句分解为多行。
参考实现
被拒绝的想法
添加带有关键字的语法
在 Python 语法中的其他所有地方,逗号分隔同质元素的变长“列表”,例如 tuple 或 list 的项、函数的参数/实参,或导入目标。在 Python 3.0 引入 except...as 之后,assert 语句仍然是这一惯例的唯一例外。
用户混淆可能至少部分源于一种期望,即逗号分隔的项是等效的。将 assert 语句的表达式和消息括在括号中,会使它们在视觉上更加紧密地结合在一起。使 assert 看起来更像函数调用会助长错误的思维方式。
作为一个可能的解决方案,有人提议 [7] 用一个关键字替换逗号,并且该形式将允许括号,例如
assert condition else "message"
assert (condition else "message")
然后可以缓慢而小心地废弃逗号,从它们出现在括号中的情况开始,这已经会引发 SyntaxWarning。
本 PEP 的作者认为,首先,添加一个全新的语法并不能解决本 PEP 旨在解决的常见初学者陷阱,也无法改善跨多行 assert 语句的格式,而作者认为提议的语法有所改进。
安全隐患
此更改不涉及安全隐患。
致谢
此更改最初在 python/cpython#90325 中讨论并提出。
非常感谢 Petr Viktorin 在起草本 PEP 过程中的帮助。
脚注
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0679.rst
最后修改: 2025-09-21 17:32:27 GMT