8.1.语法错误

又称解析错误 报错信息包括:出错的行;显示指向检测到错误位置的小箭头;文件名

8.2.异常

在执行时检测到的错误称为异常 报错信息包括:异常的类型;错误细节

8.3.异常的处理

try语句的工作原理: 1.执行try子句 2.没有触发异常,跳过except子句 3.触发异常,跳过try子句剩下部分,并且将异常与except后关键字进行匹配“如果匹配,则执行except子句;如果为未匹配,则停止执行并输出一条错误信息

特性: 一个 try 可以有多个普通 except 子句,对于一次抛出的普通异常,只有第一个匹配的 except 子句会执行。一个 except 子句中的类匹配的异常将是该类本身的实例或其所派生的类的实例(但反过来则不可以 — 列出派生类的 except 子句 不会匹配其基类的实例) 人话:基类(父类)异常可以匹配子类异常,反之不可以 so,需要主要异常匹配的顺序 异常参数 except子句可能在异常后指定一个变量(通过as),被绑定到异常实例,该实例有一个args属性,内置异常类型定义了 __str__() 来打印所有参数而不必显式地访问 .args。

异常之间的关系 BaseException 是所有异常的共同基类。它的一个子类,Exception,是所有非致命异常的基类。不是 Exception 的子类的异常通常不被处理,因为它们被用来指示程序应该终止。它们包括由 sys.exit() 引发的 SystemExit,以及当用户希望中断程序时引发的 KeyboardInterrupt。

异常匹配的注意点: Exception 可以被用作通配符,捕获(几乎)一切。然而,好的做法是,尽可能具体地说明我们打算处理的异常类型,并允许任何意外的异常传播下去。

try…except语句具有可选的else语句

异常处理程序还会处理在try子句中调用的函数的异常

8.4.触发异常

使用raise语句,强制触发指定类型的异常 唯一的参数是要触发的异常,这个参数必须是异常实例或者异常类,如果是异常类,会隐式调用无参数的构造函数来创建一个异常实例

8.5.异常链

raise a from b语句来显式表明一个异常(a)是另一个异常(b)的直接后果:此处的b必须为实例或者none,而未none时,表示禁用自动异常链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def func():
    raise ConnectionError

try:
    func()
except ConnectionError as exc:
    raise RuntimeError('Failed to open database') from exc

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    func()
    ~~~~^^
  File "<stdin>", line 2, in func
ConnectionError

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

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError('Failed to open database') from exc
RuntimeError: Failed to open database

8.6.用户自定义异常

程序可以通过创建新的异常类命名自己的异常 (Python 类的内容详见 类)。 不论是以直接还是间接的方式,异常都应从 Exception 类派生。

异常类可以被定义成能做其他类所能做的任何事,但通常应当保持简单,它往往只提供一些属性,允许相应的异常处理程序提取有关错误的信息。

大多数异常命名都以"Error"结尾,类似标准异常的命名。

许多标准模块定义了自己的异常,以报告他们定义的函数中可能出现的错误。

8.7.定义清理操作

try-finally语句:定义在所有情况下都要执行的操作,通常在异常处理中用于清理操作 一些复杂的触发异常场景,感觉没什么意思,自己看看吧

  • 如果执行 try 子句期间触发了某个异常,则某个 except 子句应处理该异常。如果该异常没有 except 子句处理,在 finally 子句执行后会被重新触发。
  • except 或 else 子句执行期间也会触发异常。同样,该异常会在 finally 子句执行之后被重新触发。
  • 如果 finally 子句执行 break、 continue 或 return 语句,异常不重新引发。这可能会引起混淆,因此不鼓励使用。从 3.14 版开始,编译器会为它发出一个 SyntaxWarning (参见 PEP 765)。
  • 如果执行 try 语句时遇到 break、continue 或 return 语句,则 finally 子句在执行 break、continue 或 return 语句之前执行。
  • 如果一个 finally 子句包含一个 return 语句,返回的值将是来自 finally 子句的 return 语句,而不是来自 try 子句的 return 语句。 这可能会引起混淆,因此不提倡使用。 从 3.14 版开始,编译器会为它发出一个 SyntaxWarning (参见 PEP 765)。

通常用于释放外部资源的逻辑

8.8.预定义的清理操作(没啥意思,自己看)

某些对象定义了不需要该对象时要执行的标准清理操作。无论使用该对象的操作是否成功,都会执行清理操作。比如,下例要打开一个文件,并输出文件内容:

1
2
for line in open("myfile.txt"):
    print(line, end="")

这个代码的问题在于,执行完代码后,文件在一段不确定的时间内处于打开状态。在简单脚本中这没有问题,但对于较大的应用程序来说可能会出问题。with 语句支持以及时、正确的清理的方式使用文件对象:

1
2
3
with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

语句执行完毕后,即使在处理行时遇到问题,都会关闭文件 f。和文件一样,支持预定义清理操作的对象会在文档中指出这一点。

8.9.引发和处理多个不相关的异常

场景:继续执行收集多个错误而不是引发第一个异常 方法一:内置的 ExceptionGroup 打包了一个异常实例的列表,这样它们就可以一起被引发。它本身就是一个异常,所以它可以像其他异常一样被捕获。 方法二:使用except替代except,有选择地处理异常,注意此处地except子句就不再是只能执行一个了

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 "<stdin>", line 2, in <module>
  |     f()
  |     ~^^
  |   File "<stdin>", line 2, in f
  |     raise ExceptionGroup(
  |     ...<12 lines>...
  |     )
  | ExceptionGroup: group1 (1 sub-exception)
  +-+---------------- 1 ----------------
    | ExceptionGroup: group2 (1 sub-exception)
    +-+---------------- 1 ----------------
      | RecursionError: 4
      +------------------------------------

注意,嵌套在一个异常组中的异常必须是实例,而不是类型。这是因为在实践中,这些异常通常是那些已经被程序提出并捕获的异常

8.10.用注释细化异常情况

add_note(note)用于在异常被捕获之后添加信息到异常地注释列表中 例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 "<stdin>", line 2, in <module>
    raise TypeError('bad type')
TypeError: bad type
Add some information
Add some more information

应用于异常组中,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 "<stdin>", line 1, in <module>
  |     raise ExceptionGroup('We have some problems', excs)
  | ExceptionGroup: We have some problems (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 1
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 2
    +---------------- 3 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 3
    +------------------------------------