2.14.3 捕获特定类型的异常

细分异常类型的存在,不仅可以让我们更准确地描述异常,还可以针对不同的类型进行不同的处理。在捕获异常的时候,我们可以指定特定的异常类型。

我们来设计一段代码:输入一个下标值,代码根据下标取出list中相应的元素,判断这个元素是否是奇数还是偶数。


def is_even_number(num_list, index):
    number = num_list[index]
    if number % 2 == 0:
        print('{} at position {} is even'.format(num_list[index], index))
        return True
    else:
        print('{} at position {} is odd'.format(num_list[index], index))
        return False

很显然,当指定的下标超出范围时,会有异常(IndexError类型)发生。


numbers = [21, 33, 34, 2, 99, 98]
is_even_number(numbers, 100)

执行结果如下:


IndexError: list index out of range

当这类异常发生的时候,我们不希望程序就此崩溃,而是给用户一个清晰的提示。


def is_even_number(num_list, index):
    try:
        number = num_list[index]
        if number % 2 == 0:
            print('{} at position {} is even'.format(num_list[index], index))
            return True
        else:
            print('{} at position {} is odd'.format(num_list[index], index))
            return False
    except IndexError:
        print("ERROR: Invalid index {}".format(index))
        return False

numbers = [21, 33, 34, 2, 99, 98]
is_even_number(numbers, 100)
is_even_number(numbers, 2)

执行结果如下:


ERROR: Invalid index 100
34 at position 2 is even

如果list的值发生了变化,混入了一个字符串“奸细”。很显然,这个字符串不能进行数学计算。在这种情况下,我们可以设计多个except逻辑,分别针对不同的异常类型进行处理。


def is_even_number(num_list, index):
    try:
        number = num_list[index]
        if number % 2 == 0:
            print('{} at position {} is even'.format(num_list[index], index))
            return True
        else:
            print('{} at position {} is odd'.format(num_list[index], index))
            return False
    except IndexError:
        print("IndexError: Invalid index {}".format(index))
        return False
    except TypeError:
        print("TypeError: Invalid value {} at index {}".format(number, index))
        return False

numbers = [21, 33, '66', 34, 2, 99, 98]
is_even_number(numbers, 100)
is_even_number(numbers, 3)
is_even_number(numbers, 2)
is_even_number(numbers, 1)

执行结果如下:


IndexError: Invalid index 100
34 at position 3 is even
TypeError: Invalid value 66 at index 2
33 at position 1 is odd

如果不同类型的异常对应的异常处理逻辑是相同的,我们可以将处理逻辑合并,归总到一个except语句中。


def is_even_number(num_list, index):
    try:
        number = num_list[index]
        if number % 2 == 0:
            print('{} at position {} is even'.format(num_list[index], index))
            return True
        else:
            print('{} at position {} is odd'.format(num_list[index], index))
            return False
    except (IndexError, TypeError):
        print("Invalid data!")
        return False

numbers = [21, 33, '66', 34, 2, 99, 98]
is_even_number(numbers, 100)
is_even_number(numbers, 3)
is_even_number(numbers, 2)
is_even_number(numbers, 1)

执行结果如下:


Invalid data!
34 at position 3 is even
Invalid data!
33 at position 1 is odd

我们已经知道,异常类型有层次关系,比如,ZeroDivisionError是一种具体的Arith-meticError类型,而ArithmeticError是一种具体的Exception类型。如果一个try语句对应多个except分支,Python会根据分支的前后顺序依次判断类型,最先匹配的分支会被处理,后续的分支判断会被忽略,也就是说,最多只会有一个except的分支会得到处理。

在这个例子中,被抛出的异常被截获,进入ArithmeticError类型的逻辑分支被处理。


try:
    print(3 / 0)
except ArithmeticError:
    print('ArithmeticError happened')
except Exception:
    print('Exception happened')

执行结果如下:


ArithmeticError happened

如果我们调整except分支的顺序,把Exception类型的分支放在前面,因为Zero-DivisionError是Exception类型,所以程序逻辑会进入Exception的分支,后续更具体的ArithmeticError分支就没有机会得到处理。


try:
    print(3 / 0)
except Exception:
    print('Exception happened')
except ArithmeticError:
    print('ArithmeticError happened')

执行结果如下:


Exception happened