Skip to content

Commit 4d0f0bd

Browse files
committed
docs(python): 增加 错误和异常 文章
1 parent 15e9887 commit 4d0f0bd

File tree

1 file changed

+379
-0
lines changed

1 file changed

+379
-0
lines changed
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
---
2+
category:
3+
- 笔记
4+
- backend
5+
tag:
6+
- python
7+
---
8+
9+
# 8. 错误和异常
10+
11+
## 8.1. 语法错误
12+
13+
语法错误又称解析错误。
14+
15+
解析器会重复出错的行并显示指向检测到错误的位置的小箭头。
16+
请注意这并不一定是需要被修复的位置。
17+
18+
## 8.2. 异常
19+
20+
即使语句或表达式使用了正确的语法,执行时仍可能触发错误。执行时检测到的错误称为 _异常_
21+
22+
[内置异常](https://docs.python.org/zh-cn/3.13/library/exceptions.html#bltin-exceptions) 列出了内置异常及其含义。
23+
24+
## 8.3. 异常的处理
25+
26+
可以编写程序处理选定的异常。下例会要求用户一直输入内容,直到输入有效的整数,但允许用户中断程序(使用 Control-C 或操作系统支持的其他操作);
27+
注意,用户中断程序会触发 [`KeyboardInterrupt`](https://docs.python.org/zh-cn/3.13/library/exceptions.html#KeyboardInterrupt) 异常。
28+
29+
:::playground#python 输入异常处理
30+
31+
@file main.py
32+
33+
```python
34+
while True:
35+
try:
36+
x = int(input("Please enter a number: "))
37+
break
38+
except ValueError:
39+
print("Oops! That was no valid number. Try again...")
40+
```
41+
42+
:::
43+
44+
[`try`](https://docs.python.org/zh-cn/3.13/reference/compound_stmts.html#try) 语句的工作原理如下:
45+
46+
- 首先,执行 `try` 子句 (`try`[`except`](https://docs.python.org/zh-cn/3.13/reference/compound_stmts.html#except) 关键字之间的(多行)语句)。
47+
- 如果没有触发异常,则跳过 `except` 子句,`try` 语句执行完毕。
48+
- 如果在执行 `try` 子句时发生了异常,则跳过该子句中剩下的部分。
49+
如果异常的类型与 `except` 关键字后指定的异常相匹配,则会执行 `except` 子句,然后跳到 `try/except` 代码块之后继续执行。
50+
- 如果发生的异常与 `except` 子句 中指定的异常不匹配,则它会被传递到外层的 `try` 语句中;如果没有找到处理器,则它是一个 未处理异常 且执行将停止并输出一条错误消息。
51+
52+
`try` 语句可以有多个 `except` 子句 来为不同的异常指定处理程序。但最多只有一个处理程序会被执行。
53+
54+
处理程序只处理对应的 `try` 子句 中发生的异常,而不处理同一 `try` 语句内其他处理程序中的异常。
55+
`except` 子句 可以用带圆括号的元组来指定多个异常,例如:
56+
57+
```python
58+
except (RuntimeError, TypeError, NameError):
59+
pass
60+
```
61+
62+
一个 `except` 子句中的类匹配的异常将是该类本身的实例或其所派生的类的实例(但反过来则不可以 --- 列出派生类的 `except` 子句 不会匹配其基类的实例)。
63+
64+
例如,下面的代码将依次打印 B, C, D:
65+
66+
```python
67+
class B(Exception):
68+
pass
69+
70+
class C(B):
71+
pass
72+
73+
class D(C):
74+
pass
75+
76+
for cls in [B, C, D]:
77+
try:
78+
raise cls()
79+
except D:
80+
print("D")
81+
except C:
82+
print("C")
83+
except B:
84+
print("B")
85+
```
86+
87+
请注意如果颠倒 `except` 子句 的顺序(把 `except B` 放在最前),则会输出 `B`, `B`, `B` --- 即触发了第一个匹配的 `except` 子句。
88+
89+
发生异常时,它可能具有关联值,即异常 _参数_ 。是否需要参数,以及参数的类型取决于异常的类型。
90+
91+
`except` 子句可能会在异常名称后面指定一个变量。 这个变量将被绑定到异常实例,该实例通常会有一个存储参数的 `args` 属性。
92+
为了方便起见,内置异常类型定义了 [`__str__()`](https://docs.python.org/zh-cn/3.13/reference/datamodel.html#object.__str__) 来打印所有参数而不必显式地访问 `.args`
93+
94+
:::playground#python 异常参数
95+
96+
@file main.py
97+
98+
```python
99+
try:
100+
raise Exception('spam', 'eggs')
101+
except Exception as inst:
102+
print(type(inst)) # 异常的类型
103+
print(inst.args) # 参数保存在 .args 中
104+
print(inst) # __str__ 允许 args 被直接打印
105+
# 但可能在异常子类中被覆盖
106+
x, y = inst .args # 解包 args
107+
print('x = ', x)
108+
print('y = ', y)
109+
# <class 'Exception'>
110+
# ('spam', 'eggs')
111+
# ('spam', 'eggs')
112+
# x = spam
113+
# y = eggs
114+
```
115+
116+
:::
117+
118+
未处理异常的 `__str__()` 输出会被打印为该异常消息的最后部分 ('detail')。
119+
120+
[BaseException](https://docs.python.org/zh-cn/3.13/library/exceptions.html#BaseException) 是所有异常的共同基类。它的一个子类, [Exception](https://docs.python.org/zh-cn/3.13/library/exceptions.html#Exception) ,是所有非致命异常的基类。
121+
不是 `Exception` 的子类的异常通常不被处理,因为它们被用来指示程序应该终止。
122+
它们包括由 `sys.exit()` 引发的 `SystemExit` ,以及当用户希望中断程序时引发的 `KeyboardInterrupt`
123+
124+
`Exception` 可以被用作通配符,捕获(几乎)一切。然而,好的做法是,尽可能具体地说明我们打算处理的异常类型,并允许任何意外的异常传播下去。
125+
126+
处理 `Exception` 最常见的模式是打印或记录异常,然后重新提出(允许调用者也处理异常):
127+
128+
:::playground#python 重新提出异常
129+
130+
@file main.py
131+
132+
```python
133+
import sys
134+
135+
try:
136+
f = open('myfile.txt')
137+
s = f.readline()
138+
i = int(s.strip())
139+
except OSError as err:
140+
print("OS error: ", err)
141+
except ValueError:
142+
print("Could not convert data to an integer.")
143+
except Exception as err:
144+
print(f"Unexpected {err=}, {type(err)=}")
145+
raise
146+
```
147+
148+
@file myfile.txt
149+
150+
```txt
151+
test
152+
```
153+
154+
:::
155+
156+
`try` ... `except` 语句具有可选的 `else` 子句,该子句如果存在,它必须放在所有 `except` 子句 之后。
157+
它适用于 `try` 子句 没有引发异常但又必须要执行的代码。例如:
158+
159+
```python
160+
for arg in sys.argv[1:]:
161+
try:
162+
f = open(arg, 'r')
163+
except OSError:
164+
print('cannot open', arg)
165+
else:
166+
print(arg, 'has', len(f.readlines()), 'lines')
167+
f.close()
168+
```
169+
170+
使用 `else` 子句比向 `try` 子句添加额外的代码要好,可以避免意外捕获非 `try` ... `except` 语句保护的代码触发的异常。
171+
172+
## 8.4. 触发异常
173+
174+
`raise` 语句支持强制触发指定的异常。
175+
176+
`raise` 唯一的参数就是要触发的异常。这个参数必须是异常实例或异常类(派生自 `BaseException` 类,例如 `Exception` 或其子类)。
177+
如果传递的是异常类,将通过调用没有参数的构造函数来隐式实例化。
178+
179+
```python
180+
raise NameError('Hi There')
181+
raise ValueError # raise Value() 的简化
182+
```
183+
184+
## 8.5. 异常链
185+
186+
如果一个未处理的异常发生在 `except` 部分内,它将会有被处理的异常附加到它上面,并包括在错误信息中。
187+
188+
为了表明一个异常是另一个异常的直接后果, raise 语句允许一个可选的 from 子句:
189+
190+
```python
191+
# exc 必须为异常类实例或 None
192+
raise RuntimeError from exc
193+
```
194+
195+
```python
196+
def func():
197+
raise ConnectionError
198+
199+
try:
200+
func()
201+
except ConnectionError as exc:
202+
raise RuntimeError('Failed to open database') from exc
203+
204+
# Traceback (most recent call last):
205+
# File "<stdin>", line 2, in <module>
206+
# func()
207+
# ~~~~^^
208+
# File "<stdin>", line 2, in func
209+
# ConnectionError
210+
#
211+
# The above exception was the direct cause of the following exception:
212+
#
213+
# Traceback (most recent call last):
214+
# File "<stdin>", line 4, in <module>
215+
# raise RuntimeError('Failed to open database') from exc
216+
# RuntimeError: Failed to open database
217+
```
218+
219+
`from None` 表达禁用自动异常链。
220+
221+
```python
222+
try:
223+
open('database.sqlite')
224+
except OSError:
225+
raise RuntimeError from None
226+
227+
# Traceback (most recent call last):
228+
# File "<stdin>", line 4, in <module>
229+
# raise RuntimeError from None
230+
# RuntimeError
231+
```
232+
233+
异常链机制详见 [内置异常](https://docs.python.org/zh-cn/3.13/library/exceptions.html#bltin-exceptions)
234+
235+
## 8.6. 用户自定义异常
236+
237+
程序可以通过创建新的异常类命名自己的异常(Python 类的内容详见 类)。不论是以直接还是间接的方式,异常都应从 `Exception` 类派生。
238+
239+
大多数异常命名都以 “Error” 结尾,类似标准异常的命名。
240+
241+
## 8.7. 定义清理操作
242+
243+
```python
244+
try:
245+
raise KeyboardInterrupt
246+
finally:
247+
print('Goodbye, world!')
248+
```
249+
250+
如果存在 `finally` 子句,则 `finally` 子句是 `try` 语句结束前执行的最后一项任务。不论 `try` 语句是否触发异常,都会执行 `finally` 子句。
251+
252+
- 如果执行 `try` 子句期间触发了某个异常,则某个 `except` 子句应处理该异常。如果该异常没有 `except` 子句处理,在 `finally` 子句执行后会被重新触发。
253+
- `except``else` 子句执行期间也会触发异常。同样,该异常会在 `finally` 子句执行之后被重新触发。
254+
- 如果 `finally` 子句中包含 `break``continue``return` 等语句,异常将不会被重新引发。
255+
- 如果执行 `try` 语句时遇到 `break`,、`continue``return` 语句,则 `finally` 子句在执行 `break``continue``return` 语句之前执行。
256+
- 如果 `finally` 子句中包含 `return` 语句,则返回值来自 `finally` 子句的某个 `return` 语句的返回值,而不是来自 `try` 子句的 `return` 语句的返回值。
257+
258+
## 8.8. 预定义的清理操作
259+
260+
某些对象定义了不需要该对象时要执行的标准清理操作。无论使用该对象的操作是否成功,都会执行清理操作。
261+
[`with`](https://docs.python.org/zh-cn/3.13/reference/compound_stmts.html#with) 语句支持以及时、正确的清理的方式使用文件对象:
262+
263+
```python
264+
with open('myfile.txt') as f:
265+
for line in f:
266+
print(line, end="")
267+
```
268+
269+
语句执行完毕后,即使在处理行时遇到问题,都会关闭文件 `f`
270+
271+
## 8.9. 引发和处理多个不相关的异常
272+
273+
在有些情况下,有必要报告几个已经发生的异常。这通常是在并发框架中当几个任务并行失败时的情况,但也有其他的用例,有时需要是继续执行并收集多个错误而不是引发第一个异常。
274+
275+
内置的 [`ExceptionGroup`](https://docs.python.org/zh-cn/3.13/library/exceptions.html#ExceptionGroup) 打包了一个异常实例的列表,这样它们就可以一起被引发。它本身就是一个异常,所以它可以像其他异常一样被捕获。
276+
277+
```python
278+
def f():
279+
excs = [OSError('error 1'), SystemError('error 2')]
280+
raise ExceptionGroup('there were problems', excs)
281+
f()
282+
# + Exception Group Traceback (most recent call last):
283+
# | File "<stdin>", line 1, in <module>
284+
# | f()
285+
# | ~^^
286+
# | File "<stdin>", line 3, in f
287+
# | raise ExceptionGroup('there were problems', excs)
288+
# | ExceptionGroup: there were problems (2 sub-exceptions)
289+
# +-+---------------- 1 ----------------
290+
# | OSError: error 1
291+
# +---------------- 2 ----------------
292+
# | SystemError: error 2
293+
# +------------------------------------
294+
try:
295+
f()
296+
except Exception as e:
297+
print(f'caught {type(e)}: e')
298+
# caught <class 'ExceptionGroup'>: e
299+
```
300+
301+
通过使用 `except*` 代替 `except` ,我们可以有选择地只处理组中符合某种类型的异常。
302+
在下面的例子中,显示了一个嵌套的异常组,每个 `except*` 子句都从组中提取了某种类型的异常,而让所有其他的异常传播到其他子句,并最终被重新引发。
303+
304+
```python
305+
def f():
306+
raise ExceptionGroup(
307+
"group1",
308+
[
309+
OSError(1),
310+
SystemError(2),
311+
ExceptionGroup(
312+
"group2",
313+
[
314+
OSError(3),
315+
RecursionError(4)
316+
]
317+
)
318+
]
319+
)
320+
321+
try:
322+
f()
323+
except* OSError as e:
324+
print("There were OSErrors")
325+
except* SystemError as e:
326+
print("There were SystemErrors")
327+
328+
# There were OSErrors
329+
# There were SystemErrors
330+
# + Exception Group Traceback (most recent call last):
331+
# | File "<stdin>", line 2, in <module>
332+
# | f()
333+
# | ~^^
334+
# | File "<stdin>", line 2, in f
335+
# | raise ExceptionGroup(
336+
# | ...<12 lines>...
337+
# | )
338+
# | ExceptionGroup: group1 (1 sub-exception)
339+
# +-+---------------- 1 ----------------
340+
# | ExceptionGroup: group2 (1 sub-exception)
341+
# +-+---------------- 1 ----------------
342+
# | RecursionError: 4
343+
# +------------------------------------
344+
```
345+
346+
注意,嵌套在一个异常组中的异常必须是实例,而不是类型。这是因为在实践中,这些异常通常是那些已经被程序提出并捕获的异常,其模式如下:
347+
348+
```python
349+
excs = []
350+
for test in tests:
351+
try:
352+
test.run()
353+
except Exception as e:
354+
excs.append(e)
355+
356+
if excs:
357+
raise ExceptionGroup('Test Failures', excs)
358+
```
359+
360+
## 8.10. 用注释细化异常情况
361+
362+
当一个异常被创建以引发时,它通常被初始化为描述所发生错误的信息。在有些情况下,在异常被捕获后添加信息是很有用的。
363+
为了这个目的,异常有一个 `add_note(note)` 方法接受一个字符串,并将其添加到异常的注释列表。
364+
标准的回溯在异常之后按照它们被添加的顺序呈现包括所有的注释。
365+
366+
```python
367+
try:
368+
raise TypeError('bad type')
369+
except Exception as e:
370+
e.add_note('Add some information')
371+
e.add_note('Add some more information')
372+
raise
373+
# Traceback (most recent call last):
374+
# File "<stdin>", line 2, in <module>
375+
# raise TypeError('bad type')
376+
# TypeError: bad type
377+
# Add some information
378+
# Add some more information
379+
```

0 commit comments

Comments
 (0)