PEP 601 – 禁止 return/break/continue 跳出 finally
- 作者:
- Damien George, Batuhan Taskaya
- 赞助商:
- Alyssa Coghlan
- 讨论列表:
- Discourse 线程
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2019年8月26日
- Python 版本:
- 3.8
- 历史记录:
- 2019年8月26日,2019年9月23日
- 决议:
- Discourse 消息
拒绝说明
该 PEP 已被指导委员会以 4/4 的投票结果拒绝。
Guido 拒绝该 PEP 的理由是:“在我看来,大多数语言都实现了这种结构,但都有样式指南和/或 linter 来拒绝它。我将支持一个提案,将此添加到PEP 8中”,以及“我注意到玩具示例有点误导——可能很有用的功能是在 finally 块中使用条件 return(或 break 等)。”。
摘要
本 PEP 提案禁止在 finally
代码块中使用 return
、break
和 continue
语句,这些语句会跳出 finally
代码块。在这样的位置使用它们会静默取消任何通过 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
会静默取消任何通过 finally
代码块传播的异常。这种行为出乎意料,一点也不明显。此函数等效于
def foo():
try:
foo()
except:
pass
return
break
和 continue
具有类似的行为(它们会使异常静默),如果它们跳转到 finally
代码块之外的代码。例如
def bar():
while True:
try:
1 / 0
finally:
break
这种行为违反了 Python 之禅的以下部分
- 显式优于隐式 - 异常被隐式地静默
- 可读性很重要 - 代码的意图不明确
- 错误绝不应该悄无声息地过去;除非明确静默 - 异常被隐式地静默
如果确实需要这种使异常静默的行为,则可以使用 try-except 的显式形式,这使得代码更清晰。
独立于语义,在 finally
代码块中实现 return
/break
/continue
并非易事,因为它需要在运行时正确跟踪任何活动异常(正在执行的 finally
代码块可能具有也可能不具有活动异常)并根据需要取消它们。CPython 在 continue
的情况下确实存在一个错误,因此最初不允许使用它[1]。要求 return
/break
/continue
在 finally
中具有正确的行为给 Python 的替代实现带来了不必要的负担。
其他语言
Java 允许从 finally
块中返回,但根据[2]、[3]、[4],不鼓励使用它。Java 编译器后来包含了一个 linting 选项 -Xlint:finally
来警告在 finally
块中使用 return。Eclipse 编辑器也会警告此用法。
Ruby 允许从 ensure(Python 的 finally)内部返回,但它应该是一个显式返回。它不被鼓励并且由 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 - 此处的用法看起来像一个错误
- Lib/multiprocessing/connection.py:316 - 此处的用法看起来是合法的,但意图不明确
- Lib/multiprocessing/connection.py:318 - 此处的用法看起来是合法的,但意图不明确
- Lib/test/test_sys_settrace.py:837 - 对
finally
中return
的测试 - Lib/test/test_sys_settrace.py:1346 - 对
finally
中return
的测试
标准库中没有在 finally
中使用 break
(跳出 finally
)的例子。
安全影响
这是对语言的简化,以及相关代码的删除,因此不应引入任何新的安全漏洞路径。
如何教授
此功能很少使用,因此禁止它可能只会影响高级用户,而不是初学者,并且可能不会影响任何现有的教学材料。由于这是删除一项功能,因此如果/当使用被禁止的功能时,通过引发 SyntaxError
来教授用户。
参考实现
目前没有参考实现,尽管 finally
中当前处理 continue
的方式(引发 SyntaxError
)可以扩展到 return
和 break
。
参考文献
版权
本文件置于公有领域或根据 CC0-1.0-通用许可证,以许可证中更为宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0601.rst
上次修改时间:2023-10-11 12:05:51 GMT