Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

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 代码块中使用 returnbreakcontinue 语句,这些语句会跳出 finally 代码块。在这样的位置使用它们会静默取消任何通过 finally 抛出的活动异常,导致代码不清楚并可能出现错误。

在 Python 3.7 中,continue 目前不支持在 finally 中使用(由于实现问题),并且该提案建议在 Python 3.8 中不添加对它的支持。对于 returnbreak,该提案建议在 Python 3.9 中弃用它们,在 Python 3.10 中发出编译警告,然后在之后禁止使用它们。

动机

finally 代码块中使用 returnbreakcontinue 会导致行为完全不明确。考虑以下函数

def foo():
    try:
        foo()
    finally:
        return

即使它具有无限递归并在 try 中引发异常,它也会干净地返回(没有异常)。原因是 finally 中的 return 会静默取消任何通过 finally 代码块传播的异常。这种行为出乎意料,一点也不明显。此函数等效于

def foo():
    try:
        foo()
    except:
        pass
    return

breakcontinue 具有类似的行为(它们会使异常静默),如果它们跳转到 finally 代码块之外的代码。例如

def bar():
    while True:
        try:
            1 / 0
        finally:
            break

这种行为违反了 Python 之禅的以下部分

  • 显式优于隐式 - 异常被隐式地静默
  • 可读性很重要 - 代码的意图不明确
  • 错误绝不应该悄无声息地过去;除非明确静默 - 异常被隐式地静默

如果确实需要这种使异常静默的行为,则可以使用 try-except 的显式形式,这使得代码更清晰。

独立于语义,在 finally 代码块中实现 return/break/continue 并非易事,因为它需要在运行时正确跟踪任何活动异常(正在执行的 finally 代码块可能具有也可能不具有活动异常)并根据需要取消它们。CPython 在 continue 的情况下确实存在一个错误,因此最初不允许使用它[1]。要求 return/break/continuefinally 中具有正确的行为给 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]

C# 禁止在 finally 中使用结束语句,如 return/goto/break[8][9]

基本原理

由于 return/break/continuefinally 中的行为不清楚,该模式很少使用,并且有一种编写等效代码的简单替代方案(更明确),因此禁止语法是最直接的方法。

规范

这是对编译器而不是语法的更改。编译器应在 finally 代码块中检查以下内容

  • 在任何语句中,任何嵌套级别上的 return
  • 在任何语句中,任何嵌套级别上的 break/continue,这些语句会将控制流转移到 finally 代码块之外。

找到这种情况后,它应该发出相应的异常

  • 对于 continueSyntaxError(这是 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 而静默)。

向后兼容性

对于 returnbreak,这是一个向后不兼容的更改。

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 - 对 finallyreturn 的测试
  • Lib/test/test_sys_settrace.py:1346 - 对 finallyreturn 的测试

标准库中没有在 finally 中使用 break(跳出 finally)的例子。

安全影响

这是对语言的简化,以及相关代码的删除,因此不应引入任何新的安全漏洞路径。

如何教授

此功能很少使用,因此禁止它可能只会影响高级用户,而不是初学者,并且可能不会影响任何现有的教学材料。由于这是删除一项功能,因此如果/当使用被禁止的功能时,通过引发 SyntaxError 来教授用户。

参考实现

目前没有参考实现,尽管 finally 中当前处理 continue 的方式(引发 SyntaxError)可以扩展到 returnbreak

参考文献


来源:https://github.com/python/peps/blob/main/peps/pep-0601.rst

上次修改时间:2023-10-11 12:05:51 GMT