PEP 348 – Python 3.0 异常重组
- 作者:
- Brett Cannon <brett at python.org>
- 状态:
- 已拒绝
- 类型:
- 标准轨迹
- 创建:
- 2005年7月28日
- 发布历史:
目录
- 摘要
- 需要更改的原因
- 重组理念
- 新的层次结构
- 与 Python 2.4 的区别
- 用于
raise
的必需超类 - 裸
except
子句捕获 Exception - 过渡计划
- 被拒绝的想法
- DeprecationWarning 继承自 PendingDeprecationWarning
- AttributeError 继承自 TypeError 或 NameError
- 移除 EnvironmentError
- 引入 MacError 和 UnixError
- SystemError 作为 SystemExit 的子类
- ControlFlowException 在 Exception 下
- 将 NameError 重命名为 NamespaceError
- 重命名现有异常
- 使 EOFError 成为 IOError 的子类
- 使 MemoryError 和 SystemError 拥有共同的超类
- PendingDeprecationWarning 和 DeprecationWarning 的共同超类
- 移除 WindowsError
- KeyboardInterrupt 和 SystemExit 的超类
- 致谢
- 参考文献
- 版权
注意
此 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 继承
被拒绝的想法
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