Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 348 – Python 3.0 的异常重组

作者:
Brett Cannon <brett at python.org>
状态:
已拒绝
类型:
标准跟踪
创建日期:
2005 年 7 月 28 日
发布历史:


目录

注意

此 PEP 已被拒绝 [16]

摘要

自 Python 2.4 版本起,内置命名空间中有 38 个异常(包括警告),层级结构相当浅。这些类经过多年发展,没有机会从经验中学习。本 PEP 建议在 Python 3.0 中重组层级结构,此时向后兼容性不再是主要问题。

除了此次重组之外,还建议要求所有传递给 raise 语句的对象都必须继承自一个特定的超类。这是为了保证异常的基本接口,并进一步增强异常的自然层级结构。

最后,裸 except 子句将被修改为在语义上等同于 except Exception。大多数人目前都将裸 except 子句用于此目的,并且随着异常层级结构的重组,它成为一个可行的默认设置。

更改理由

异常是 Python 的关键部分。虽然异常传统上用于表示程序中的错误,但它们也已用于流控制,例如迭代器。

虽然它们很重要,但却缺乏结构。这源于任何对象都可以作为异常被抛出。因此,您无法保证将抛出何种类型的对象,从而破坏了任何可能被抛出对象遵守的层级结构。

但异常确实有一个层级结构,显示了异常的严重性。该层级结构还将相关异常分组在一起,以简化在 except 子句中捕获它们。为了让人们能够依赖此层级结构,正在提议一个所有抛出对象都必须继承的共同超类。它还允许对抛出对象的接口进行保证(参见 PEP 344)。所有这些的讨论之前已在 python-dev 上进行过 [1]

目前的裸 except 子句会捕获*所有*异常。虽然这很方便,但对于常见情况来说过于宽泛。由于有了必需的超类,捕获所有异常就像捕获一个特定异常一样简单。这使得裸 except 子句可以用于更有用的目的。再次强调,这已在 python-dev 上讨论过 [2]

最后,对异常层级结构的微小更改将使其在结构上更加合理。通过对异常进行细微的重新排列,那些通常不应被捕获的异常可以被允许传播到执行堆栈的顶部,从而按预期终止解释器。

重组理念

对于层级结构的重组,遵循了从本 PEP 早期草案的讨论中发展出来的一般理念 [4][5][6][7][8][9]。首先也是最重要的是不破坏任何正常工作的东西。这意味着重命名异常是不可取的,除非名称被认为严重不佳。这也意味着除非异常被认为是真正放错了位置,否则不会移除异常。只有在可能需要捕获某类异常的超类的情况下,才会引入新异常。最后,现有异常的继承树只会在它们最初就被认为真正放错了位置的情况下才会更改。

对于所有新异常,必须选择正确的后缀。对于表示错误的异常,使用“Error”。如果异常是警告,则使用“Warning”。当其他后缀都不合适且没有更合适的特定后缀时,使用“Exception”。

之后,就归结为选择哪些异常应该和不应该继承自 Exception。这是为了使裸 except 子句更有用。

最后,整个现有层级结构必须继承自旨在作为所有异常必须继承的所需超类的新异常。

新层级结构

注意

标有“更严格继承”的异常将不再继承自某个特定类。“更广泛继承”标志意味着已将一个类添加到该异常的继承树中。所有比较均以 Python 2.4 异常层级结构为基准。

+-- BaseException (new; broader inheritance for subclasses)
    +-- Exception
        +-- GeneratorExit (defined in PEP 342)
        +-- StandardError
            +-- ArithmeticError
                +-- DivideByZeroError
                +-- FloatingPointError
                +-- OverflowError
            +-- AssertionError
            +-- AttributeError
            +-- EnvironmentError
                +-- IOError
                +-- EOFError
                +-- OSError
            +-- ImportError
            +-- LookupError
                +-- IndexError
                +-- KeyError
            +-- MemoryError
            +-- NameError
                +-- UnboundLocalError
            +-- NotImplementedError (stricter inheritance)
            +-- SyntaxError
                +-- IndentationError
                    +-- TabError
            +-- TypeError
            +-- RuntimeError
            +-- UnicodeError
                +-- UnicodeDecodeError
                +-- UnicodeEncodeError
                +-- UnicodeTranslateError
            +-- ValueError
            +-- ReferenceError
        +-- StopIteration
        +-- SystemError
        +-- Warning
            +-- DeprecationWarning
            +-- FutureWarning
            +-- PendingDeprecationWarning
            +-- RuntimeWarning
            +-- SyntaxWarning
            +-- UserWarning
        + -- WindowsError
    +-- KeyboardInterrupt (stricter inheritance)
    +-- SystemExit (stricter inheritance)

与 Python 2.4 的差异

在讨论继承更改时,需要更彻底地解释术语。继承更改会导致更广泛或更严格的继承。“更广泛”是指一个类的继承树为 cls, A,然后变为 cls, B, A。“更严格”则相反。

BaseException

所有异常都必须继承的超类。其名称的选择是为了反映它位于异常层级结构的基部,同时它本身也是一个异常。“Raisable”曾被考虑作为名称,但因为它不能正确反映它本身就是一个异常的事实而被放弃。

不期望直接继承 BaseException,并且通常会不鼓励这样做。大多数用户定义的异常应该继承自 Exception。这使得捕获 Exception 在捕获所有应捕获的异常的常见情况下仍然有效。直接继承 BaseException 只应在需要全新类别的异常的情况下进行。

但是,对于所有异常都应盲目捕获的情况,except BaseException 将会起作用。

KeyboardInterrupt 和 SystemExit

这两个异常都不再属于 Exception。这是为了让裸 except 子句通过捕获继承自 Exception 的异常来充当更可行的默认情况。由于 KeyboardInterrupt 和 SystemExit 都表示解释器预期会退出,因此在常见情况下捕获它们是错误的语义。

NotImplementedError

继承自 Exception 而不是 RuntimeError。

NotImplementedError 最初继承自 RuntimeError,与旨在在用户代码中用作快速而粗略的异常没有任何直接关系。因此,它现在直接继承自 Exception。

raise 所需的超类

通过要求传递给 raise 语句的所有对象都继承自一个特定的超类,可以保证所有异常都具有某些属性。如果 PEP 344 被接受,其中概述的属性将保证存在于所有抛出的异常上。这应该通过使从异常中查询信息变得更容易来帮助调试。

提议的层级结构以 BaseException 作为必需的基类。

实施

强制执行很简单。修改 RAISE_VARARGS 以在抛出异常之前先进行继承检查就足够了。对于 C API,所有设置异常的函数都将应用相同的继承检查。

except 子句捕获 Exception

在大多数现有的 Python 2.4 代码中,裸 except 子句捕获的异常范围太广。通常只希望捕获表示错误的异常。这意味着在常见情况下不应捕获用于表示解释器应退出的异常。

随着 KeyboardInterrupt 和 SystemExit 被移至继承自 BaseException 而非 Exception,将裸 except 子句更改为等同于 except Exception 成为一个更合理的默认值。这一更改也将很少破坏现有代码,因为这些语义是大多数人对裸 except 子句所期望的。

已经有人主张完全移除裸 except 子句。理由是它们违反了《Python 之禅》中列出的“只有一种方法可以做到”(OOWTDI)和“显式优于隐式”(EIBTI)原则。但“实用胜于纯粹”(PBP),同样在《Python 之禅》中,在这种情况下胜过这两个原则。BDFL 已经声明裸 except 子句将以这种方式工作 [14]

实施

当遇到裸 except 子句时,编译器将发出 except Exception 的字节码。

过渡计划

由于为本 PEP 中计划的所有功能添加所需的高度复杂性和混乱,过渡计划非常简单。在 Python 2.5 中添加 BaseException。在 Python 3.0 中,所有剩余的功能(必需的超类、继承更改、裸 except 子句与 except Exception 相同)将生效。为了使所有这些在 Python 2.5 中以向后兼容的方式工作,将需要对异常机制进行非常深入的修改,这可能会容易出错,并导致性能下降,而收益甚微。

为了帮助过渡,文档将进行更改以反映以下几项编程指南:

  • 当想要捕获*所有*异常时,捕获 BaseException
  • 要捕获所有不表示解释器终止的异常,请显式捕获 Exception
  • 显式捕获 KeyboardInterrupt 和 SystemExit;不要依赖 Exception 的继承来导致捕获
  • 始终显式捕获 NotImplementedError,而不是依赖 RuntimeError 的继承

“exceptions”模块 [3]、教程 [15]PEP 290 的文档都将需要更新。

被拒绝的想法

DeprecationWarning 继承自 PendingDeprecationWarning

这最初被提议是因为 DeprecationWarning 可以被视为将在下一个版本中移除的 PendingDeprecationWarning。但是由于足够多的人认为这种继承逻辑上可以反过来,所以这个想法被放弃了。

AttributeError 继承自 TypeError 或 NameError

将属性视为类型接口的一部分,导致了继承 TypeError 的想法。但这部分违背了鸭子类型的思想,因此这个想法被放弃了。

有人建议继承自 NameError,因为对象可以被视为拥有自己的命名空间,属性 reside 于其中,当找不到属性时,就是命名空间失败。这个可能性也被放弃了,因为并非所有人都认同这种观点。

移除 EnvironmentError

最初是基于EnvironmentError是一个不必要的区别的观点提出的,但BDFL否决了这个想法 [10]

引入 MacError 和 UnixError

为了与 WindowsError 对称而提出,BDFL 表示它们不会被足够多地使用 [10]。然后提出了移除 WindowsError 的想法,并被认为是合理的,从而完全否定了添加这些异常的想法。

SystemError 子类化 SystemExit

由于 SystemError 旨在导致系统退出而提出,但由于 CriticalError 更能表明这一点,所以该想法被移除。

ControlFlowException 在 Exception 之下

有人建议 ControlFlowException 应该继承自 Exception。这个想法被拒绝了,因为控制流异常通常不需要由单个 except 子句全部捕获。

将 NameError 重命名为 NamespaceError

NameError 被认为更简洁,并且不会出现“Namespace”大写错误的可能 [11]

重命名 RuntimeError 或引入 SimpleError

RuntimeError 的想法是,当一种情况不需要创建新异常时,它绝不是一个明显的异常名称。由于该异常已在整个解释器中使用,因此重命名被拒绝 [12]。拒绝 SimpleError 是基于这样的想法:人们应该可以自由选择使用任何异常,而不是有一个如此明显被建议的异常 [13]

重命名现有异常

提出了各种重命名建议,但没有一个获得超过 +0 票(将 ReferenceError 重命名为 WeakReferenceError)。理由是现有名称很好,从未有人主动抱怨过。为了最大限度地减少向后兼容性问题并避免给现有 Python 程序员带来额外痛苦,重命名被取消。

让 EOFError 子类化 IOError

最初的想法是,由于 EOFError 直接处理 I/O,它应该子类化 IOError。但由于 EOFError 更多地用作一个事件发生(I/O 端口耗尽)的信号,因此它不应子类化如此特定的错误异常。

让 MemoryError 和 SystemError 拥有共同的超类

这两个类都与解释器有关,那为什么不让它们拥有共同的超类呢?因为其中一个意味着解释器处于不应恢复的状态,而另一个则不然。

PendingDeprecationWarning 和 DeprecationWarning 的共同超类

将弃用警告异常分组在一起,这在直觉上是合理的。但是,当考虑到这两个警告很少被使用,更不用说同时使用了,这种合理性就大打折扣了。

移除 WindowsError

最初提出是基于这样的想法,即这种特定于平台的异常不应该存在于内置命名空间中。然而事实证明,现有代码中使用了足够多的该异常,足以使其保留下来。

KeyboardInterrupt 和 SystemExit 的超类

为了更容易捕获不继承 Exception 的异常,并简化向新层次结构的过渡,BDFL 拒绝了这一想法 [14]。理由是现有代码中没有足够的示例表明这对异常被捕获,因此不值得占用内置命名空间。

致谢

感谢 Robert Brewer、Josiah Carlson、Alyssa Coghlan、Timothy Delaney、Jack Diedrich、Fred L. Drake, Jr.、Philip J. Eby、Greg Ewing、James Y. Knight、MA Lemburg、Guido van Rossum、Stephen J. Turnbull、Raymond Hettinger,以及所有我遗漏的参与讨论的人。

参考资料


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

最后修改: 2025-02-01 08:59:27 GMT