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]

摘要

截至 2.4 版本,Python 在内置命名空间中拥有 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)。但同样在 Python 之禅中提到的“实用性胜于纯粹性”(PBP)在这种情况下胜过这两者。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 继承,因为对象可以被视为拥有自己的命名空间,属性存在于其中,当找不到属性时,就是一个命名空间错误。由于并非所有人都赞同这种观点,因此这也作为一种可能性被放弃了。

移除 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

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