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
+------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术