为什么处理多个异常需要一个元组而不是一个列表?
用 C 语言编写的错误处理,在其他类型检查和异常处理之前对元组的特殊情况使用类型检查,以便可以捕获多种类型的异常。
至少有一位 Python 核心开发人员主张对控制流使用异常处理。将列表添加为要检查的附加类型将违反此策略。
似乎核心开发团队尚未专门解决将其扩展为允许集合或列表的问题,但如果可以找到它,我会很乐意参考它。关于Python mailing list 的讨论进行了很多推测(此处的另一个答案详细引用了一个响应)。
在进行了下面的分析之后,在邮件列表讨论的背景下,我认为推理是显而易见的。我不建议添加其他容器。
列出失败与元组的演示
exceptions = TypeError, RuntimeError
list_of_exceptions = list(exceptions)
捕获异常元组确实有效:
try:
raise TypeError('foo')
except exceptions as error:
print(error)
输出:
foo
但是捕获异常列表不起作用:
try:
raise TypeError('foo')
except list_of_exceptions as error:
print(error)
打印:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: foo
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
TypeError: catching classes that do not inherit from BaseException is not allowed
这表明我们正在对元组的特殊情况进行类型检查。添加另一种类型进行检查肯定会使代码变慢,并且核心开发人员一直在说use exception handling for control flow in Python 一段时间以来是件好事。
源码分析
对来源的分析与上述结论一致。
语法
这不是 Python 的 grammar 或解析的问题。它将接受任何表达式。因此,任何导致异常或异常元组的表达式都应该是合法的。
反汇编
如果我们在 Python 3 中反汇编一个执行此操作的函数,我们会看到它看起来将异常与比较操作相匹配。
def catch(exceptions):
try:
raise Exception
except exceptions:
pass
import dis
dis.dis(catch)
哪些输出:
2 0 SETUP_EXCEPT 10 (to 13)
3 3 LOAD_GLOBAL 0 (Exception)
6 RAISE_VARARGS 1
9 POP_BLOCK
10 JUMP_FORWARD 18 (to 31)
4 >> 13 DUP_TOP
14 LOAD_FAST 0 (exceptions)
17 COMPARE_OP 10 (exception match)
...
这引导我们进入 Python 解释器。
内部控制流程——CPython的实现细节
CPython 控制流先checks for if the value is a tuple. 如果是,
它使用特定于元组的代码遍历元组——寻找异常的值:
case PyCmp_EXC_MATCH:
if (PyTuple_Check(w)) {
Py_ssize_t i, length;
length = PyTuple_Size(w);
for (i = 0; i < length; i += 1) {
PyObject *exc = PyTuple_GET_ITEM(w, i);
if (!PyExceptionClass_Check(exc)) {
_PyErr_SetString(tstate, PyExc_TypeError,
CANNOT_CATCH_MSG);
return NULL;
}
}
}
else {
if (!PyExceptionClass_Check(w)) {
_PyErr_SetString(tstate, PyExc_TypeError,
CANNOT_CATCH_MSG);
return NULL;
}
}
res = PyErr_GivenExceptionMatches(v, w);
break;
添加另一种类型将需要更多的内部控制流,从而减慢 Python 解释器内部的控制流。
Python 容器的大小
Tuples are lightweight arrays of pointers。列表也是如此,但它们可能会被分配额外的空间,以便您可以快速添加到它们(直到它们需要变得更大)。在 Linux 上的 Python 3.7.3 中:
>>> from sys import getsizeof
>>> getsizeof((1,2,3))
72
>>> getsizeof([1,2,3])
88
集合占用更多空间,因为它们是哈希表。它们既有所包含对象的哈希值,也有指向它们所指向对象的指针。
结论
这是由 CPython 核心开发团队讨论和决定的。
但我的结论是,即使在 C 级别检查其他类型来减慢 Python 中的控制流,也会违背在 Python 模块中对控制流使用异常处理的策略。
经过上面的推理,我不建议他们添加这个。