考虑以下示例:

def main_list(error_type):

    try:
        if error_type == 'runtime':
            raise RuntimeError("list error")
        if error_type == 'valueerror':
            raise ValueError("list error")

    except [RuntimeError, ValueError] as e:
        print str(e)

def main_tuple(error_type):

    try:
        if error_type == 'runtime':
            raise RuntimeError("tuple error")
        if error_type == 'valueerror':
            raise ValueError("tuple error")

    except (RuntimeError, ValueError) as e:
        print str(e)


main_tuple('runtime')
main_tuple('valueerror')

main_list('runtime')
main_list('valueerror')

元组是处理多种异常类型的正确方法。对多个异常类型使用列表不会导致任何异常。

我想知道为什么Python语法需要多个异常类型的元组。 docs说它使用一个元组,因此也许只是“从未使用列表而不是元组来实现”。

在我看来,至少在概念上也可以在这种情况下使用列表。

在这种情况下,Python是否有任何理由为什么使用元组而不是列表?

最佳答案



用C编写的错误处理在其他类型检查和异常处理之前对元组的特殊情况使用类型检查,以便可以捕获多种类型的异常。

至少一位Python核心开发人员提倡对控制流使用异常处理。将列表添加为要检查的其他类型将违反此策略。

核心开发团队似乎并没有专门解决将其扩展为允许集合或列表的问题,但是如果可以找到它,我将很乐意引用它。关于Python mailing list的讨论有很多推测(这里的另一个答案详细引用了一个响应)。

在执行以下分析之后,并在邮件列表讨论的背景下,我认为推理是显而易见的。我不建议添加其他容器。

列出失败与元组的演示

exceptions = TypeError, RuntimeError
list_of_exceptions = list(exceptions)

捕获异常的元组确实起作用:

try:
    raise TypeError('foo')
except exceptions as error:
    print(error)

输出:

foo

但是捕获异常(exception)列表不起作用:

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或解析,这不是问题。它将接受任何表达式。因此,任何导致Exception或Exception元组的表达式都应合法。

拆卸

如果我们反汇编一个在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模块中对控制流使用异常处理的策略背道而驰。

经过以上推理,我不建议他们添加此内容。

关于python - 为什么处理多个异常需要一个元组而不是一个列表?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35851782/

10-11 22:46
查看更多