PEP 601 – 禁止在 finally 块中使用 return/break/continue 跳出
- 作者:
- Damien George, Batuhan Taskaya
- 发起人:
- Alyssa Coghlan
- 讨论至:
- Discourse 帖子
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2019年8月26日
- Python 版本:
- 3.8
- 发布历史:
- 2019年8月26日, 2019年9月23日
- 取代者:
- 765
- 决议:
- Discourse 消息
拒绝说明
此 PEP 已被指导委员会投票否决,比率为 4/4。
Guido 否决此 PEP 的论点是:“在我看来,大多数语言都实现了这种构造,但有风格指南和/或 Linter 拒绝它。我支持将此添加到 PEP 8 的提案”,以及“我注意到玩具示例有些误导——可能有用的功能是在 finally 块中的条件 return(或 break 等)。”。
摘要
此 PEP 提议禁止在 finally 块中使用会跳出 finally 的 return、break 和 continue 语句。在 such a location 使用它们会 silently 取消通过 finally 传播的任何活动异常,导致代码不清晰且可能出现错误。
在 Python 3.7 中,continue 当前在 finally 中不受支持(由于实现问题),而此提案是不在 Python 3.8 中添加对其的支持。对于 return 和 break,提案是在 Python 3.9 中弃用其使用,在 Python 3.10 中发出编译警告,然后禁止其使用。
动机
finally 块中 return、break 和 continue 的使用会导致行为不明显。考虑以下函数
def foo():
try:
foo()
finally:
return
即使它有无限递归并在 try 中引发异常,此函数也会干净地返回(不带异常)。原因是 finally 中的 return 会 silently 取消任何通过 finally 块传播的异常。这种行为是出乎意料且不明显的。此函数等同于
def foo():
try:
foo()
except:
pass
return
如果 break 和 continue 跳到 finally 块外部的代码,它们会产生类似的行为(它们会 silently 抑制异常)。例如
def bar():
while True:
try:
1 / 0
finally:
break
这种行为违背了《Python 之禅》的以下部分:
- 显式优于隐式——异常被隐式地抑制
- 可读性很重要——代码的意图不明显
- 错误永远不应悄悄地发生;除非明确抑制——异常被隐式地抑制
如果确实需要这种抑制异常的行为,那么可以使用显式的 try-except 形式,这使得代码更清晰。
与语义无关,在 finally 块中实现 return/break/continue 并非易事,因为它需要在运行时正确跟踪任何活动的异常(正在执行的 finally 块可能有也可能没有活动异常)并适当地取消它们。CPython 在 continue 的情况下存在一个 bug,因此最初不允许它 [1]。要求在 finally 中正确处理 return/break/continue 会给 Python 的其他实现带来不必要的负担。
其他语言
Java 允许从 finally 块返回,但根据 [2]、[3]、[4],不鼓励使用。Java 编译器后来包含了 -Xlint:finally 这一 linting 选项来警告在 finally 块中使用 return。Eclipse 编辑器也对此用法发出警告。
Ruby 允许从 ensure(Python 的 finally)内部返回,但它应该是显式的 return。这是不被鼓励的,并且由 Linter 处理 [5]、[6]。
与 Ruby 类似,JavaScript 也允许在 finally 块中使用 return/break/continue,但这被视为不安全,并且由 eslint 处理 [7]。
基本原理
由于 return/break/continue 在 finally 块中的行为不明确,并且该模式很少使用,而且编写等效代码(更显式)有简单的替代方案,因此禁止该语法是最直接的方法。
规范
这是对编译器的更改,而不是对语法的更改。编译器应检查 finally 块中的以下内容:
- 在任何嵌套级别的任何语句中使用
return。 - 在任何嵌套级别的任何语句中使用
break/continue,它会将控制流转移到finally块外部。
一旦发现这种情况,它应发出适当的异常:
- 对于
continue,SyntaxError(这是 3.7 的当前行为)。 - 对于
return/break,3.10 中为SyntaxWarning,之后为SyntaxError。
例如,以下所有内容都将被此提案禁止:
def f():
try:
pass
finally:
return
def g():
try:
pass
finally:
try:
return
finally:
pass
def h():
try:
pass
finally:
try:
pass
finally:
for x in range(10):
return
以下情况仍然允许,因为 continue 没有跳出 finally 块:
try:
pass
finally:
for x in range(10):
continue
请注意,此 PEP 仍然允许从 finally 块中 yield,因为恢复生成器将恢复 finally 并最终引发任何活动异常(因此它们永远不会被 yield 抑制)。
向后兼容性
这对于 return 和 break 来说是一个不兼容的向后更改。
在 CPython 标准库(v3.8.0b1-651-g7fcc2088a5)中,以下位置在 finally 块中使用 return:
- Lib/subprocess.py:921 - 这里的用法看起来像一个 bug
- Lib/multiprocessing/connection.py:316 - 这里的用法看起来合法,但意图不明确
- Lib/multiprocessing/connection.py:318 - 这里的用法看起来合法,但意图不明确
- Lib/test/test_sys_settrace.py:837 - 一个测试
return在finally块中的用法 - Lib/test/test_sys_settrace.py:1346 - 一个测试
return在finally块中的用法
在标准库中,没有在 finally 块中使用 break(会跳出 finally 块)的用法。
安全隐患
这是对语言的简化,以及相关代码的移除,因此不应引入新的安全漏洞。
如何教授此内容
此功能很少使用,因此禁止它很可能只会影响高级用户,而不是初学者,也不会影响现有的教学材料。由于这是对一个功能的移除,当用户使用被禁止的功能时,会引发 SyntaxError,这将是用户学习的方式。
参考实现
目前没有参考实现,尽管 continue 在 finally 中当前的处理方式(引发 SyntaxError)可以扩展到 return 和 break。
参考资料
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0601.rst