参考:
语法错误
即SyntaxError
异常
异常有不同的类型,而类型名称会作为错误信息的一部分中打印出来
异常的处理
try...except...语句,和c/c++中的try catch差不多。
except 子句 可以用带圆括号的元组来指定多个异常。例如:
... except (RuntimeError, TypeError, NameError):
... pass
如果发生的异常与 except 子句中的类是同一个类或是它的基类时,则该类与该异常相兼容
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 B
放在最前,则会打印B,B,B。因为B是C/D的基类,因此满足前面的条件“except语句中的类是发生的异常的类的基类”。注意:前面手册中的中文翻译描述的不太清楚,应该按照我这里写的内容去理解。
try ... except 语句具有可选的 else 子句,该子句如果存在,它必须放在所有 except 子句 之后。 它适用于 try 子句 没有引发异常但又必须要执行的代码。
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
上面的代码打开文件的时候,检查是否文件存在。如果存在,才执行else中的读取文件的行数功能。
触发异常
raise 语句支持强制触发指定的异常。
如果只想判断是否触发了异常,但并不打算处理该异常,则可以使用更简单的 raise 语句重新触发异常。
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
...
An exception flew by!
>>>
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
比较上面例子中有raise和没有raise的区别。注意到raise依然抛出的是“raise NameError('HiThere')”这句中的异常,因此是“重新触发异常”。
异常链
就是在except子句中再次raise一个异常。在异常打印中,会将这条链上的每一个raise的异常都打印出来。
用户自定义异常
用户可以自己定义自己的异常类,但都应该直接或间接地从Exception 类派生。
大多数异常命名都以 “Error” 结尾,类似标准异常的命名。
定义清理操作
如果存在 finally 子句,则 finally
子句是 try 语句结束前执行的最后一项任务。不论 try
语句是否触发异常,都会执行 finally
子句。以下内容介绍了几种比较复杂的触发异常情景:
- 如果执行
try
子句期间触发了某个异常,则某个 except 子句应处理该异常。如果该异常没有except
子句处理,在finally
子句执行后会被重新触发。(理解:如果异常没有except子句处理,异常会被系统处理,且finally
子句的执行位于系统处理之前) except
或else
子句执行期间也会触发异常。 同样,该异常会在finally
子句执行之后被重新触发。(TODO:)- 如果
finally
子句中包含 break、continue 或 return 等语句,异常将不会被重新引发。 - 如果执行
try
语句时遇到 break、continue 或 return 语句,则finally
子句在执行break
、continue
或return
语句之前执行。(理解:break/continue/return可以认为是try语句块的退出,因此会在退出之前执行finally子句) - 如果
finally
子句中包含return
语句,则返回值来自finally
子句的某个return
语句的返回值,而不是来自try
子句的return
语句的返回值。(理解::因为finally子句会先于try中的return执行,因此先执行的是finally子句中的return,也就直接return了,所以不会再执行try中的return了)
>>> def bool_return():
... try:
... return True
... finally:
... return False
...
>>> bool_return()
False
理解上面的规则后,教程中的“复杂的例子”不难理解。
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
在实际应用程序中,finally 子句对于释放外部资源(例如文件或者网络连接)非常有用,无论是否成功使用资源。
预定义的清理操作
with语句对于文件操作的自动清理
引发和处理多个不相关的异常
内置的 ExceptionGroup 打包了一个异常实例的列表,这样它们就可以一起被引发。
>>> 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')
... raise
...
caught <class 'ExceptionGroup'>: e
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 2, in <module>
| File "<stdin>", line 3, in f
| ExceptionGroup: there were problems (2 sub-exceptions)
+-+---------------- 1 ----------------
| OSError: error 1
+---------------- 2 ----------------
| SystemError: error 2
+------------------------------------
上面的例子中,在excs这个序列中定义了2个异常,因此raise的时候使用了ExceptionGroup打包了这个异常列表。在try...except...语句中,如果except子句中没有raise,则只会打印“caught <class 'ExceptionGroup'>: e”。
通过使用 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")
# Printings as below
There were OSErrors
There were SystemErrors
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| File "C:\D\03_programTry\pyExec\test_try_except_star.py", line 19, in <module>
| f()
| File "C:\D\03_programTry\pyExec\test_try_except_star.py", line 2, in f
| raise ExceptionGroup(
| ExceptionGroup: group1 (1 sub-exception)
+-+---------------- 1 ----------------
| ExceptionGroup: group2 (1 sub-exception)
+-+---------------- 1 ----------------
| RecursionError: 4
+------------------------------------
上面的例子中,如果没有“RecursionError(4)”,则只会打印"There were OSErrors"和"There were SystemErrors"。也就是说,使用except *之后,系统对异常的打印只会发生在没有出现在自定义的except捕获对象中
用注释细化异常情况
对于捕获的异常,定义 了方法add_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)
...
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| ExceptionGroup: We have some problems (3 sub-exceptions)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| File "<stdin>", line 2, in f
| OSError: operation failed
| Happened in Iteration 1
+---------------- 2 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| File "<stdin>", line 2, in f
| OSError: operation failed
| Happened in Iteration 2
+---------------- 3 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| File "<stdin>", line 2, in f
| OSError: operation failed
| Happened in Iteration 3
+------------------------------------
上面的例子中,如果没有“raise ExceptionGroup('We have some problems', excs)”,则循环中捕获的3个异常都会被添加到序列excs中,并且每次都添加了“Happened in Iteration X”的信息。此时不会有任何打印,因为所有发生的异常都被自定义的try...except...捕获了。一旦主动raise以序列excs为基础创建的ExceptionGroup时,系统就会处理这个异常组,从而给出系统默认的打印。