5. 例外情况
接下来我们将关注异常。

5.1. 脚本 [exceptions_01]
第一个脚本说明了处理异常的必要性。
| # on provoque volontairement une erreur
x = 4 / 0
|
我们故意触发一个错误,以观察会发生什么(exceptions-01.py):
| C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py", line 2, in <module>
x = 4 / 0
ZeroDivisionError: division by zero
Process finished with exit code 1
|
第 4 行显示:
- 异常类型:ZeroDivisionError;
- 相关的错误信息:除以零。该信息为英文。您可能需要修改这一点。
一条关键规则是:我们必须尽一切可能避免由 Python 解释器触发的异常。我们需要自行处理错误。
异常处理的语法如下:
try:
actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
actions de gestion de l'exception [ex]
finally:
actions toujours exécutées qu'il y ait exception ou non
在 try 代码块中,一旦发生异常(错误),操作的执行将立即停止。此时,执行将转至其中一个 except 子句中的操作:
- 第 3 行:如果发生的异常 [ex] 属于元组 (Ex1, Ex2…) 中的类型或其派生类型,则执行第 4 行的操作;
- 第 5 行:如果该异常未被第 3 行捕获,且存在另一个 [except] 子句,则执行相同的处理流程。以此类推;
- [except] 子句的数量可根据需要任意增加,以处理 [try] 代码块内可能发生的各类异常;
- 如果该异常未被任何 [except] 子句处理,则会回传至调用代码。若调用代码本身位于 try/except 结构中,则再次处理该异常;否则,异常将继续沿被调用方法的调用链向上传播。最终,异常将到达 Python 解释器。此时解释器将终止正在运行的程序,并显示如前例所示类型的错误信息。 因此,规则是主程序必须捕获所有可能从被调用方法传播过来的异常;
- 第 7 行:无论是否发生异常(在 `except` 之后)或未发生异常(在 `try` 之后),`[finally]` 子句都会被执行。即使发生了异常但未被捕获,情况也是如此。在这种情况下,`[finally]` 子句将在异常回传至调用代码之前被执行;
异常携带有关所发生错误的信息。可通过以下语法获取这些信息:
except MyException as exception:
[exception] 是发生的异常。[exception.args] 表示该异常参数的元组。
要抛出异常,请使用以下语法
raise MyException(param1, param2…)
其中 MyException 通常是继承自 BaseException 类的子类。传递给该类构造函数的参数,在异常处理结构的 except 子句中可通过 [ex.args] 语法访问,前提是 [ex] 是 [except] 子句捕获的异常。
以下脚本演示了这些概念。
5.2. 脚本 [exceptions_02]
以下脚本显式处理了错误:
| # exception handling
essai = 0
# cause an error and manage it
x = 2
try:
x = 4 / 0
except ZeroDivisionError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}")
# the value of x has not changed
print(f"x={x}")
# here we go again
essai += 1
try:
x = 4 / 0
except BaseException as erreur:
# we intercept the most general exception
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}")
# different types of exceptions can be intercepted
# execution stops on the 1st [except] able to handle the exception
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# this exception does not occur here
print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
# we intercept the most general exception
print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
# we intercept a specific type
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
# start again, changing the order
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# this exception does not occur here
print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
# we intercept a specific type
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
# we intercept the most general exception
print(f"essai n° {essai} : (Exception) {erreur}")
# an except clause with no arguments
essai += 1
try:
x = 4 / 0
except:
# we're not interested in the nature of the exception
print(f"essai n° {essai} : il y a eu un problème")
# another type of exception
essai += 1
try:
# x cannot be converted to an integer
x = int("x")
except ValueError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}")
# an exception transports information in a tuple accessible to the program
essai += 1
try:
x = int("x")
except ValueError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# exceptions can be thrown
essai += 1
try:
raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# you can create your own exceptions
# they must derive from class [BaseException]
class MyError(BaseException):
pass
# throw MyError exception
essai += 1
try:
raise MyError("info1", "info2", "info3")
except MyError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# throw MyError exception with error msg
essai += 1
try:
raise MyError("mon msg d'erreur")
except MyError as erreur:
# error is the exception intercepted
print(f"essai n° {essai} : {erreur.args[0]}")
# the finally clause is always executed
# whether or not there is an exception
essai += 1
x = None
try:
x = 1
except:
# exception
print(f"essai n° {essai} : exception")
finally:
# executed in all cases
print(f"essai n° {essai} : finally x={x}")
essai += 1
x = None
try:
x = 2 / 0
except:
# exception
print(f"essai n° {essai} : exception")
finally:
# executed in all cases
print(f"essai n° {essai} : finally x={x}")
# we don't have to include an except clause
essai += 1
try:
# we cause an error
x = 4 / 0
finally:
# executed in all cases
print(f"essai n° {essai} : finally x={x}")
|
注:
- 第 4–12 行:处理除以零的情况;
- 第 8 行:捕获发生的具体异常并显示它;
- 第 12 行:由于发生了异常,第 7 行中的 x 未获得值,因此其值未发生改变;
- 第 14–21 行:我们执行相同操作,但捕获类型为 BaseException 的更高层级异常。由于 ZeroDivisionError 异常是 BaseException 的子类,因此 except 子句将捕获它;
- 第 23–36 行:我们使用多个 except 子句来处理多种异常类型。仅会执行其中一个 except 子句,若异常与任何 except 子句均不匹配,则不会执行任何子句;
- 第 38–50 行:我们采用相同的方法,通过调整 [except] 子句的顺序来展示它们的作用;
- 第 52–58 行:`except` 子句可以不带参数。在这种情况下,它会捕获所有异常;
- 第 60–67 行:引入 ValueError 异常;
- 第 69–75 行:我们获取异常携带的信息;
- 第 77–83 行:介绍如何抛出异常;
- 第 86–106 行:演示自定义异常类 MyError 的用法。MyError 类仅继承自基类 BaseException,并未向基类添加任何内容。但现在可以在 `except` 子句中显式指定该类;
- 第 108–130 行:演示 finally 子句的使用;
- 第 132–139 行:这些行表明 [except] 子句并非强制要求;
屏幕输出如下:
| C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py", line 136, in <module>
x = 4 / 0
ZeroDivisionError: division by zero
essai n° 0 : division by zero
x=2
essai n° 1 : division by zero
essai n° 2 : (Exception) division by zero
essai n° 3 : (ZeroDivisionError) division by zero
essai n° 4 : il y a eu un problème
essai n° 5 : invalid literal for int() with base 10: 'x'
essai n° 6 : invalid literal for int() with base 10: 'x', paramètres=("invalid literal for int() with base 10: 'x'",)
essai n° 7 : ('param1', 'param2', 'param3'), paramètres=('param1', 'param2', 'param3')
essai n° 8 : ('info1', 'info2', 'info3'), paramètres=('info1', 'info2', 'info3')
essai n° 9 : mon msg d'erreur
essai n° 10 : finally x=1
essai n° 11 : exception
essai n° 11 : finally x=None
essai n° 12 : finally x=None
Process finished with exit code 1
|
5.3. 脚本 [exceptions_03]
这个新脚本演示了异常如何沿调用函数链向上传播:
| # a proprietary exception
class MyError(BaseException):
pass
# three functions
def f1(x: int) -> int:
# exceptions are not handled - they are automatically raised
return f2(x)
def f2(y: int) -> int:
# exceptions are not handled - they are automatically raised
return f3(y)
def f3(z: int) -> int:
if (z % 2) == 0:
# if z is even, throw an exception
raise MyError("exception dans f3")
else:
return 2 * z
# ---------- hand
# exceptions go up the chain of methods called
# until a method intercepts it. In this case, it will be main
try:
print(f1(4))
except MyError as erreur:
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# three other functions that enrich the exceptions it tracks
def f4(x: int) -> int:
try:
return f5(x)
except MyError as erreur:
# we enrich the exception then relaunch it
raise MyError("exception dans f4", erreur)
def f5(y: int) -> int:
try:
return f6(y)
except MyError as erreur:
# we enrich the exception then relaunch it
raise MyError("exception dans f5", erreur)
def f6(z: int) -> int:
if (z % 2) == 0:
# throw an exception if z is even
raise MyError("exception dans f6")
else:
return 2 * z
# ---------- hand
try:
print(f4(4))
except MyError as erreur:
# exception display
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# you can move up the exception stack
err = erreur
# error msg is displayed
print(err.args[0])
# is an exception encapsulated?
while len(err.args) == 2 and isinstance(err.args[1], BaseException):
# exception change
err = err.args[1]
# the 1st argument is the error msg
print(err.args[0])
|
注:
- 第 25–32 行:在调用 main → f1 → f2 → f3(第 30 行)的过程中,由 f3 引发的 MyError 异常将向上传播至 main。随后,该异常将由第 31 行的 except 子句进行处理;
- 第 61–75 行:在 main → f4 → f5 → f6 的调用链中(第 62 行),由 f6 抛出的 MyError 异常将向上传播至 main。随后,它将由第 63 行的 except 子句进行处理。这一次,随着异常沿调用函数链向上传播,该 MyError 异常本身被封装在另一个异常之中;
- 第 66–75 行:演示如何追踪异常堆栈;
- 第 71 行:函数 [isinstance(instance, Class)] 返回 True,当对象 [instance] 属于类型 [Class] 或其子类时。此处我们使用了最高级别的异常 [BaseException],以确保捕获所有异常;
屏幕输出如下:
| C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_03.py
type : <class '__main__.MyError'>, arguments : ('exception dans f3',)
type : <class '__main__.MyError'>, arguments : ('exception dans f4', MyError('exception dans f5', MyError('exception dans f6')))
exception dans f4
exception dans f5
exception dans f6
Process finished with exit code 0
|