Exception Handling(异常处理)

python有许多内置异常。比如我们常见的TypeError, AttributeError, ValueError等等。 实际上所有的异常都源自一个基类BaseException。 注意并不是Exception类。我们一般在异常处理时捕获的称之为Concrete exceptions,用Exception可以捕获所有这些 Concrete exceptions。
各种异常并不是毫无关系的,有些异常是有继承关系的。 比如ModuleNotFoundError是ImportError的子类。 except子句中使用ImportError可以同时捕获ImportError和MoudleNotFoundError这两种异常。

但像Syntax Error这种异常是语法错误,python解释器会立即抛出,根本不会运行到我们的try ... catch语句里。
下面的示例是一个语法错误

while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
               ^^^^^
SyntaxError: invalid syntax

try ... except 语句进行异常捕获和处理。

try:
    x = int('abc')
except ValueError:
    print("Oops!  That was no valid number.  Try again...")

但如果except里没对捕获到的异常类型做处理,只异常会继续抛出。

try:
    x = int('abc')
except NameError:
    print("处理一个NameError")

运行结果:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in <module>
    x = int('abc')
        ^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'abc'

使用except... as 来引用异常实例
except子句可以捕获多种异常。Exception代表所有类型异常。 要想引用异常,使用as语句,将异常实例存储到变量中。

try:
    x = int('abc')
except NameError:
    print("处理一个NameError")
except AttributeError:
    print("处理一个ValueError")
except Exception as e:
    print(f"处理一个{type(e)}")

输出结果:
处理一个<class 'ValueError'>

用元组同时处理多种异常
还可以用元组来同时处理多种异常。

try:
    x = int('abc')
except (NameError, AttributeError, ValueError) as e:
    print(f"处理一个{type(e)}")

输出结果:
处理一个<class 'ValueError'>

异常的继承关系
有些异常之间存在继承关系,比如except子句使用ImportError可以捕获ModuleNotFoundError:

try:
    print('do something before error')
    raise ModuleNotFoundError
except ImportError as e:
    print(f'got ImportError: {type(e)}')

输出结果:

do something before error
got ImportError: <class 'ModuleNotFoundError'>

自定义异常
下面演示一下自定义异常,及其继承关系的用法。

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

输出结果:

B
C
D

异常的except子句里的顺序也重要,一般我们会先处理具体的异常,然后处理一般的异常。因为一旦走到一个异常类型分支,就不会再处理后面的except子句。
比如我们将上面的例子中的except子句的顺序做下调整,将B放在上面,由于B是C和D的基类,所以异常都在except B这里处理了,不会执行后面的except子句。

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except B:
        print("B")
    except D:
        print("D")
    except C:
        print("C")

输出结果如下:

B
B
B

用raise抛出异常
当我们使用raise加一个Error类时,其实python会自动将其实例化成一个异常实例。 即raise ModuleNotFoundError 等价于 raise ModuleNotFoundError(),并且通常异常类型都至少有一个参数用来实例化异常消息。

try:
    print('do something before error')
    raise ModuleNotFoundError("some module not found")
except ImportError as e:
    print(f'got ImportError: {e}')

输出结果:

do something before error
got ImportError: some module not found

其实在异常内部有个args属性,里面存储了异常的信息。比如我们主动抛出的异常时加的描述性信息。具体到不同的异常时,args属性数量可能不同。python的内置异常都在其__str__方法里帮我们实现了对args属性里内容的输出,所以我们直接print(e)时就能看到这些属性内容。

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))    # the exception type
    print(inst.args)     # arguments stored in .args
    print(inst)          # __str__ allows args to be printed directly,
                         # but may be overridden in exception subclasses
    x, y = inst.args     # unpack args
    print('x =', x)
    print('y =', y)

输出结果:

<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

有时我们在except部分做些异常处理,比如记录日志后,想继续抛出异常,可以在except部分直接使用raise,后面不加异常类型或实例。

try:
    print('do something before error')
    raise ModuleNotFoundError("some module not found")
except ImportError as e:
    print(f'got ImportError: {e}')
    raise

输出结果:

do something before error
got ImportError: some module not found
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 3, in <module>
    raise ModuleNotFoundError("some module not found")
ModuleNotFoundError: some module not found

可以看到,Traceback里提示的异常抛出位置为原始的异常抛出位置,而不是except里的rasie语句所在行。

exception chains(异常链)
有时候,在异常处理时,可能会引发新的异常,这时我们可以从Traceback中看到这样一句话 During handling of the above exception, another exception occurred:

try:
    print('do something before error')
    raise ModuleNotFoundError("some module not found")
except ImportError as e:
    print(f'got ImportError: {e}')
    raise NameError('NameError from except statement')

输出结果:

do something before error
got ImportError: some module not found
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 3, in <module>
    raise ModuleNotFoundError("some module not found")
ModuleNotFoundError: some module not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 6, in <module>
    raise NameError('NameError from except statement')
NameError: NameError from except statement

raise ... from...
有时如果是我们使用raise语句抛出异常,并有意告知此异常是在处理另一个异常时引发的,可以使用raise new_exception from original_exception语句。这时你会看到这句: The above exception was the direct cause of the following exception:

try:
    print('do something before error')
    raise ModuleNotFoundError("some module not found")
except ImportError as e:
    print(f'got ImportError: {e}')
    raise NameError('NameError from except statement') from e

输出结果:

do something before error
got ImportError: some module not found
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 3, in <module>
    raise ModuleNotFoundError("some module not found")
ModuleNotFoundError: some module not found

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 6, in <module>
    raise NameError('NameError from except statement') from e
NameError: NameError from except statement

raise exception from None
如果我们想有意斩断异常链,并只返回最后一个异常,可以使用raise exception from None。

try:
    print('do something before error')
    raise ModuleNotFoundError("some module not found")
except ImportError as e:
    print(f'got ImportError: {e}')
    raise NameError('NameError from except statement') from None

输出结果:

do something before error
got ImportError: some module not found
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 6, in <module>
    raise NameError('NameError from except statement') from None
NameError: NameError from except statement

可以看到,一开始抛出的ModuleNotFoundError在Traceback中已经不见了。

try ... except ... else ... finally ...
完整的try statement可包含try, except, else, finally四部分。 else部分是当没有遇到异常时会执行,有异常就不会执行。
请看下面两个示例:

try:
    print('do something without error')
    # raise ModuleNotFoundError("some module not found")
except ImportError as e:
    print(f'got ImportError: {e}')
else:
    print('do something in else section')

输出结果:

```python
do something without error
do something in else section

try:
print('do something before error')
raise ModuleNotFoundError("some module not found")
except ImportError as e:
print(f'got ImportError: {e}')
else:
print('do something in else section')

输出结果:
```CSS
do something before error
got ImportError: some module not found

finally部分在是否遇到异常时都会执行。比如下面这个简单示例:

try:
    raise KeyboardInterrupt
finally:
    print('Goodbye, world!')

输出结果:

Goodbye, world!
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in <module>
    raise KeyboardInterrupt
KeyboardInterrupt

一般finally部分的代码都是写比如,关闭文件,关闭网络链接等工作。 所以就发明了with语句来更优雅的代替try...finally...这种语句。

含有break, continue, return的复杂情况
下面介绍一些更复杂的情况。
1.当except部分没有捕获异常时,异常继续向外层抛出。

try:
    raise NameError('造了一个NameError')
except ValueError:
    print('处理了一个ValueError')

输出结果:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\test.py", line 2, in <module>
    raise NameError('造了一个NameError')
NameError: 造了一个NameError

2.当有finally部分,则异常会在执行完finally里的代码后再抛出。

try:
    raise NameError('造了一个NameError')
except ValueError:
    print('处理了一个ValueError')
finally:
    print('do something in finally section')

输出结果:

do something in finally section
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\test.py", line 2, in <module>
    raise NameError('造了一个NameError')
NameError: 造了一个NameError

3.当try部分的代码执行时遇到break, continue, return时,如果存在finally代码,则在执行它们之前,要先执行finally的代码。

def bool_return():
    try:
        print('do something in try section')
        return True
    finally:
        print('do something in finally section')


print(bool_return())

输出结果:

do something in try section
do something in finally section
True

从执行结果可以看出,虽然try部分执行到了return语句,但仍然在return之前先执行了finally部分的代码。

4.当finally部分有break, continue, return语句时,未处理的异常不会再被重新抛出。 下面以return举例:

def do_something():
    try:
        raise NameError('造了一个NameError')
    except ValueError:
        print('处理了一个ValueError')
    finally:
        print('do something in finally section')
        return 'function finished'

print(do_something())

输出结果:

do something in finally section
function finished

可以看到,函数里未处理的异常并没有再被抛出到外层,因为finally部分的代码在执行时遇到了return。

5.如果try和finally部分代码里都含有break, continue, return语句,基于上面的那个示例,我们知道会先执行finally部分的代码,再回过去执行try里的return。但由于finally里也有return,最终就会在finally的return语句结束整个try statement。

def bool_return():
    try:
        print('do something in try section')
        return True
    finally:
        print('do something in finally section')
        return False

print(bool_return())

输出结果:

do something in try section
do something in finally section
False

ExceptionGroup
我们可以将一组不相关的异常放在一个异常组中,并同时抛出。更多用于并发和异步编程的情况下。

excs = [OSError('error 1'), SystemError('error 2')]
raise ExceptionGroup('there were problems', excs)

输出结果:

  + Exception Group Traceback (most recent call last):
  |   File "F:\RolandWork\PythonProjects\studyPython\test.py", line 2, in <module>
  |     raise ExceptionGroup('there were problems', excs)
  | ExceptionGroup: there were problems (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | OSError: error 1
    +---------------- 2 ----------------
    | SystemError: error 2
    +------------------------------------

ExceptionGroup也是Exception的子类,也可以在except中进行捕获。

def f():
    excs = [OSError('error 1'), SystemError('error 2')]
    raise ExceptionGroup('there were problems', excs)

try:
    f()
except Exception as e:
    print(f'caught {type(e)}: e')

输出结果:
caught <class 'ExceptionGroup'>: e

使用except* 语句可以同时处理多个exception。

def f():
    excs = [OSError('error 1'), SystemError('error 2'), NameError('error 3')]
    raise ExceptionGroup('there were problems', excs)

try:
    f()
except* OSError:
    print('deal with OSError')
except* SystemError:
    print('deal with SystemError')
except* NameError:
    print('deal with NameError')

输出结果:

deal with OSError
deal with SystemError
deal with NameError

未使用except*处理的异常会再被抛出。

def f():
    excs = [OSError('error 1'), SystemError('error 2'), NameError('error 3')]
    raise ExceptionGroup('there were problems', excs)

try:
    f()
except* OSError:
    print('deal with OSError')
except* SystemError:
    print('deal with SystemError')

输出结果:

deal with OSError
deal with SystemError
  + Exception Group Traceback (most recent call last):
  |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 6, in <module>
  |     f()
  |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 3, in f
  |     raise ExceptionGroup('there were problems', excs)
  | ExceptionGroup: there were problems (1 sub-exception)
  +-+---------------- 1 ----------------
    | NameError: error 3
    +------------------------------------

ExceptionGroup还能嵌套其它ExceptionGroup。但如果它们之间有重叠的异常,在except*部分只对应有一处来处理就够了。

def f():
    raise ExceptionGroup(
        "group1",
        [
            OSError(1),
            SystemError(2),
            ExceptionGroup(
                "group2",
                [
                    OSError(3),
                    RecursionError(4)
                ]
            )
        ]
    )

try:
    f()
except* RecursionError as e:
    print("There were RecursionError")
except* OSError as e:
    print("There were OSErrors")
except* SystemError as e:
    print("There were SystemErrors")

输出结果:

There were RecursionError
There were OSErrors
There were SystemErrors

可以看到,外层和内层的exception group里都出现了OSError,非重复的异常就三种,都有对应的except*部分在处理。
如果有未被except*处理的异常,则会继续向外层抛出。

def f():
    raise ExceptionGroup(
        "group1",
        [
            OSError(1),
            SystemError(2),
            ExceptionGroup(
                "group2",
                [
                    OSError(3),
                    RecursionError(4)
                ]
            )
        ]
    )

try:
    f()
except* OSError as e:
    print("There were OSErrors")
except* SystemError as e:
    print("There were SystemErrors")

输出结果:

There were OSErrors
There were SystemErrors
  + Exception Group Traceback (most recent call last):
  |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 18, in <module>
  |     f()
  |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in f
  |     raise ExceptionGroup(
  | ExceptionGroup: group1 (1 sub-exception)
  +-+---------------- 1 ----------------
    | ExceptionGroup: group2 (1 sub-exception)
    +-+---------------- 1 ----------------
      | RecursionError: 4
      +------------------------------------

Exceptions with Notes
我们可以在捕获到异常时,对异常添加一些备注。这种情况一般多用于想要将异常继续向外层抛出,抛出之前想加些注解的情况。

try:
    raise TypeError('bad type')
except Exception as e:
    e.add_note('Add some information')
    e.add_note('Add some more information')
    raise

输出结果:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in <module>
    raise TypeError
TypeError
Add some information
Add some more information

可以看到,在异常处理部分,我们使用exception.add_note()方法对异常填加了信息,并再次进行抛出。后面再捕获到这个异常的地方就可以看到我们填加到的信息了。

import logging

try:
    try:
        raise TypeError('bad type')
    except Exception as e:
        e.add_note('Add some information')
        e.add_note('Add some more information')
        raise
except Exception as e:
    logging.error('log some info')
    logging.exception(e)

输出结果:

ERROR:root:log some info
ERROR:root:bad type
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 5, in <module>
    raise TypeError('bad type')
TypeError: bad type
Add some information
Add some more information

下面展示了另一种可能的使用场景。将捕获到的每个异常添加note,然后追加到一个list中。之后将使用列表中的异常构造一个ExceptionGroup。再手动抛出这个ExceptionGroup后,我们就会在Traceback中看到这些添加到各个异常的note了。

def f():
    raise OSError('operation failed')

excs = []
for i in range(3):
    try:
        f()
    except Exception as e:
        e.add_note(f'Happened in Iteration {i+1}')
        excs.append(e)

raise ExceptionGroup('We have some problems', excs)

输出结果:

 + Exception Group Traceback (most recent call last):
  |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 12, in <module>
  |     raise ExceptionGroup('We have some problems', excs)
  | ExceptionGroup: We have some problems (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 7, in <module>
    |     f()
    |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 1
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 7, in <module>
    |     f()
    |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 2
    +---------------- 3 ----------------
    | Traceback (most recent call last):
    |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 7, in <module>
    |     f()
    |   File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 3
    +------------------------------------
posted @   RolandHe  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示