为什么处理多个异常需要元组而不是列表?
用 C 编写的错误处理在其他类型检查和异常处理之前对元组的特殊情况使用类型检查,以便可以捕获多种类型的异常。
至少一位 Python 核心开发人员提倡使用异常处理来控制流。添加列表作为附加类型进行检查将违背此策略。
核心开发团队似乎还没有专门解决扩展它以允许集合或列表的问题,尽管如果可以找到的话我会很乐意引用它。曾经有过一场讨论Python 邮件列表 https://mail.python.org/pipermail/python-list/2012-January/thread.html#619107这推测了很多(这里的另一个答案详细引用了一个答案)。
在执行以下分析后,并在邮件列表讨论的背景下,我认为推理是显而易见的。我不建议提议添加其他容器。
列表与元组失败的演示
exceptions = TypeError, RuntimeError
list_of_exceptions = list(exceptions)
捕获异常元组确实有效:
try:
raise TypeError('foo')
except exceptions as error:
print(error)
outputs:
foo
但是捕获异常列表不起作用:
try:
raise TypeError('foo')
except list_of_exceptions as error:
print(error)
prints:
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
这表明我们正在对元组的特殊情况进行类型检查。添加另一种类型来检查肯定会使代码变慢,核心开发人员一直在说这是一件好事在 Python 中使用异常处理来控制流 https://stackoverflow.com/a/16138864/541136有一段时间了。
源码分析
对消息来源的分析也同意上述结论。
Grammar
这对于 Python 来说不是问题grammar https://docs.python.org/3/reference/grammar.html或解析。它会接受任何表达式。因此,任何导致异常或异常元组的表达式都应该是合法的。
拆卸
如果我们反汇编在 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 控制流程检查该值是否是元组。 https://github.com/python/cpython/blob/master/Python/ceval.c#L5108如果是这样,
它使用元组特定代码迭代元组 - 查找异常值:
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 容器的大小
元组是轻量级的指针数组 https://stackoverflow.com/a/30316760/541136。列表也是如此,但它们可能会被分配额外的空间,以便您可以快速添加它们(直到它们需要变得更大)。在 Linux 上的 Python 3.7.3 中:
>>> from sys import getsizeof
>>> getsizeof((1,2,3))
72
>>> getsizeof([1,2,3])
88
集合占用更多空间,因为它们是哈希表。它们既有所包含对象的哈希值,也有指向它们所指向对象的指针。
结论
这是由 CPython 核心开发团队讨论和决定的。
但我的结论是,即使在 C 级别,通过检查其他类型来减慢 Python 中的控制流也会违背在 Python 模块中对控制流使用异常处理的策略。
经过以上推理,我不建议他们添加这个。