• Python学习笔记 - 异常处理


    前言

            为了增强程序的健壮性,计算机程序的编写也需要考虑如何处理一些异常的情况,Python 语言提供了异常处理功能,本博文主要介绍 Python 异常处理机制

    一 异常处理举例

    为了学习 Python 异常处理机制,首先看下面进行除法运算的示例。在 Python Shell 中代码如下:

    1. >>> i = input('请输入数字: ') # --1
    2. 请输入数字: 0
    3. >>> print(i)
    4. 0
    5. >>> print(5 / int(i))
    6. Traceback (most recent call last):
    7. File "", line 1, in
    8. ZeroDivisionError: division by zero
    9. >>>

    上述代码第1处通过 input() 函数从控制台读取字符串,该函数语法如下:

    input([prompt])

    prompt 参数是提示字符串,可以省略。

    从控制台读取字符串 0 赋值给变量 i,当执行 print(5 / (int)i) 语句时,会抛出 ZeroDivisionError 异常,ZeroDivisionError 是除 0 异常。这是因为在数学上除数不能为 0,所以执行表达式 (5 / (int)a) 会有异常。

    重新输入如下字符串:

    1. >>> i = input('请输入数字: ')
    2. 请输入数字: QWE
    3. >>> print(i)
    4. QWE
    5. >>> print(5 / int(i))
    6. Traceback (most recent call last):
    7. File "", line 1, in
    8. ValueError: invalid literal for int() with base 10: 'QWE'
    9. >>>

    这次输入的是字符串 QWE,因为它不能转换为整数类型,因此会抛出 ValueError 异常。

    程序运行过程中难免会发生异常,发生异常并不可怕,程序员应该考虑到有可能会发生这些异常,编程时应处理这些异常,不能让程序终止,这才是健壮的程序。

    二 异常类继承层次

    Python 中异常根类是 BaseException,异常类继承层次如下所示:

    从异常类的继承层次可见,BaseException 的子类很多,其中 Exception 是非系统退出的异常,它包含了很多常用异常。如果自定义异常需要继承 Exception 及其子类,不要直接继承 BaseException 根类。另外,还有一类异常是 Warning,Warning 是警告,提示程序潜在问题。

    提示】从异常类继承的层次可见,Python 中的异常类命名主要是后缀有 Exception、Error 和 Warning,也有少数几个没有采用这几个后缀命名的,当然这些后缀命名的类都有它的含义。但是有些中文资料根据异常类后缀名有时翻译为“异常”,有时翻译为“错误”,为了不引起误会,本博文将它们统一为 “异常”,特殊情况会另加说明。

    三 常见异常

    Python 有很多异常,熟悉几个常用异常是有必要的。本节就介绍几个常见异常。

    3.1 AttributeError 异常

    AttributeError 异常试图访问一个类中不存在的成员(包括:成员变量、属性和成员方法)而引发的异常

    1. >>> class Animal(object): # --1
    2. ... pass
    3. ...
    4. >>> a1 = Animal() # --2
    5. >>> a1.run()
    6. Traceback (most recent call last):
    7. File "", line 1, in
    8. AttributeError: 'Animal' object has no attribute 'run'
    9. >>> print(a1.age) # --3
    10. Traceback (most recent call last):
    11. File "", line 1, in
    12. AttributeError: 'Animal' object has no attribute 'age'
    13. >>> print(Animal.weight) # --4
    14. Traceback (most recent call last):
    15. File "", line 1, in
    16. AttributeError: type object 'Animal' has no attribute 'weight'
    17. >>>

    (1)上述代码第1处是定义 Animal 类。

    (2)代码第2处是试图访问 Animal 类的 run() 方法,由于 Animal 类中没有定义 run() 方法,结果抛出了 AttributeError 异常。

    (3)代码第3处是试图访问 Animal 类的实例变量(或属性)age,结果抛出了 AttributeError 异常。

    (4)代码第4处是试图访问 Animal 类的类变量 weight,结果抛出了 AttributeError 异常。

    3.2 OSError 异常

            OSError 是操作系统相关异常。Python 3.3 版本后 IOError(输入输出异常)也并入到 OSError 异常,所以输出输出异常也属于 OSError 异常。例如,“未找到文件” 或 “磁盘已满” 异常。

    在 Python Shell 中执行如下代码:

    1. >>> f = open('abc.txt')
    2. Traceback (most recent call last):
    3. File "", line 1, in
    4. FileNotFoundError: [Errno 2] No such file or directory: 'abc.txt'
    5. >>>

    上述代码 f = open('abc.txt') 是打开当前目录下的 abc.txt 文件,由于不存在该文件,所以抛出 FileNotFoundError 异常。FileNotFoundError 属于 OSError 异常,前者是后者的子类。

    3.3 IndexError 异常

    IndexError 异常是访问序列元素时,下标索引超出取值范围所引发的异常

    在 Python Shell 中执行如下代码:

    1. >>> code_list = [125, 56, 89, 36]
    2. >>> code_list[4]
    3. Traceback (most recent call last):
    4. File "", line 1, in
    5. IndexError: list index out of range
    6. >>>

    上述代码 code_list[4] 试图访问 code_list 列表的第5个元素,由于 code_list 列表最多只有 4 个元素,所以会引发 IndexError 异常。

    3.4 KeyError 异常

    KeyError 异常是试图访问字典里不存在的键时而引发的异常

    在 Python Shell 中执行如下代码:

    1. >>> dict1 = {101:'aaa', 102:'bbb', 103:'ccc'}
    2. >>> dict1[104]
    3. Traceback (most recent call last):
    4. File "", line 1, in
    5. KeyError: 104
    6. >>>

    上述代码 dict1[104] 试图访问字典 dict1 中键为 104 的值,但 104 键在字典 dict1 中不存在,所以引发了 KeyError 异常。

    3.5 NameError 异常

    NameError 是试图使用一个不存在的变量而引发的异常

    在 Python Shell 中执行如下代码:

    1. >>> value1 # --1
    2. Traceback (most recent call last):
    3. File "", line 1, in
    4. NameError: name 'value1' is not defined
    5. >>> a = value1 # --2
    6. Traceback (most recent call last):
    7. File "", line 1, in
    8. NameError: name 'value1' is not defined
    9. >>> value1 = 10 # --3
    10. >>> print(value1) # --4
    11. 10
    12. >>>

    上述代码第1处和第2处都是读取 value1 变量值,由于之前没有创建过 value1,所以会引发 NameError 异常。但是代码第3处 value1 = 10 却不会引发异常,那是因为赋值时,如果变量 value1 不存在就会创建它,所以不会引发异常。代码第4处打印出了变量 value1 的值。

    3.6 TypeError 异常

    TypeError 是试图传入变量类型与要求不符合时而引发的异常

    在 Python Shell 中执行如下代码:

    1. >>> i = '2'
    2. >>> print(5 / i) # --1
    3. Traceback (most recent call last):
    4. File "", line 1, in
    5. TypeError: unsupported operand type(s) for /: 'int' and 'str'
    6. >>>

    上述代码第1处 (5 / i) 表达式进行除法运算,需要变量 i 是一个数字类型,然而传入的却是一个字符串类型,所以引发 TypeError 异常。

    3.7 ValueError 异常

    ValueError 异常是由于传入一个无效的参数值而引发的异常。在 Python Shell 中执行如下代码:

    1. >>> i = 'QWE'
    2. >>> print (5 / int(i)) # --1
    3. Traceback (most recent call last):
    4. File "", line 1, in
    5. ValueError: invalid literal for int() with base 10: 'QWE'
    6. >>>

    上述代码第1处 (5 / int(i)) 表达式进行除法运算,需要传入的变量 i 是能够使用 int() 函数转换为数字的参数。然而传入的字符串 'QWE' 不能转换成数字,所以引发了 ValueError 异常。

    四 捕获异常

            Python 对待异常的处理方式是:当前函数有能力解决,则捕获异常后自行进行处理;如果没有能力处理,则抛给上层调用者(函数)进行处理。如果上层调用者还无力解决,则继续抛给它的上层调用者。异常就是这样向上传递直到有函数处理它。如果所有的函数都没有处理该异常,那么 Python 解释器会终止程序运行。这就是 Python 的异常传播过程。

    4.1 try-except 语句

    捕获异常是通过 try-except 语句实现的,最基本的 try-except 语句语法如下:

    1. try:
    2. <可能会抛出异常的语句>
    3. except [异常类型]:
    4. <处理异常语句>

    1)try 代码块

    try 代码块中包含执行过程中可能会抛出异常的语句。

    2)except 代码块

    每个 try 代码块可能伴随一个或多个 except 代码块,用于处理 try 代码块中所有可能抛出的多种异常。except 语句中如果省略 “异常类型”,则会捕获所有类型的异常;如果指定具体类型的异常,则会捕获该类型异常,以及它的子类型异常

    示例:一个 try-except 示例程序。

    1. # coding=utf-8
    2. # 代码文件:捕获异常示例/try_except.py
    3. import datetime as dt # --1
    4. def read_date(in_date): # --2
    5. try:
    6. date = dt.datetime.strptime(in_date, '%Y-%m-%d') # --3
    7. return date
    8. except ValueError as e: # --4
    9. print('处理 ValueError 异常')
    10. print(e)
    11. str_date = '2022-10-15'
    12. # str_date = '202B-10-15'
    13. print('日期 = {0}'.format(read_date(str_date)))

    上述代码第 1 处导入了 datetime 模块,datetime 是 Python 内置的日期时间模块。代码第 2 处定义了一个函数,在函数中将传入的字符串转换为日期,并进行格式化。但并非所有的字符串都是有效的日期字符串,因此调用代码第3处的 strptime() 方法有可能抛出 ValueError 异常。代码第4处是捕获 ValueError 异常。本示例中的 '2022-10-15' 字符串是有效的日期字符串,因此不会抛出异常。如果将字符串改为无效的日期字符串,如 '202B-10-15',则会打印以下信息。

    1. > python try_except.py
    2. 处理ValueError异常
    3. time data '202B-10-15' does not match format '%Y-%m-%d'
    4. 日期 = None

    其中,ValueError as e 中的 e 是异常对象,print(e) 语句可以打印异常对象,打印异常对象会输出如下的异常信息。

    time data '202B-10-15' does not match format '%Y-%m-%d'

     4.2 多 except 代码块

    如果 try 代码块中有很多语句抛出异常,而且抛出的异常种类有很多,那么可以在 try 后面跟有多个 except 代码块。多 except 代码块语法如下:

    1. try:
    2. <可能会抛出异常的语句>
    3. except [异常类型1]:
    4. <处理异常语句>
    5. except [异常类型2]:
    6. <处理异常语句>
    7. ...
    8. except [异常类型n]:
    9. <处理异常语句>

    在多个 except 代码块情况下,当一个 except 代码块捕获到一个异常时,其他的 except 代码块就不再进行匹配。

    注意:当捕获的多个异常类之间存在父子关系时,捕获异常顺序与 except 代码块的顺序有关。从上到下先捕获子类,后捕获父类,否则子类捕获不到。

    示例代码如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_except2.py
    3. # 多except代码块示例
    4. import datetime as dt
    5. def read_date_from_file(filename): # --1
    6. try:
    7. file = open(filename) # --2
    8. in_date = file.read() # --3
    9. in_date = in_date.strip() # --4
    10. date = dt.datetime.strptime(in_date, '%Y-%m-%d') # --5
    11. return date
    12. except ValueError as e: # --6
    13. print('处理 ValueError 异常')
    14. print(e)
    15. except FileNotFoundError as e: # --7
    16. print('处理 FileNotFountError 异常')
    17. print(e)
    18. except OSError as e: # --8
    19. print('处理 OSError 异常')
    20. print(e)
    21. date = read_date_from_file('readme.txt')
    22. print('日期 = {0}'.format(date))

             上述代码通过 open() 函数从文件 read.txt 中读取字符串,然后解析成为日期。在调用 open() 函数时是有可能会抛出异常的,所以将其放在 try 代码块中。

            在 try 代码块中,代码第 1 处定义函数 read_date_from_file(filename) 用来从文件中读取字符串,并解析成为日期。代码第2处调用 open() 函数读取文件,它有可能抛出 FileNotFoundError 异常等 OSError 异常。如果抛出 FileNotFoundError 异常,则会被代码第 7 处的 except 捕获。如果抛出 OSError 异常,则会被代码第 8 处的 except 捕获。代码第 3 处 file.read() 方法是从文件中读取数据,它也可能抛出 OSError 异常。如果抛出 OSError 异常,则会被代码第8处的 except 捕获。代码第 4 处的 in_date.strip() 方法是剔除字符串前后空白字符(包括空格、制表符、换行和回车等字符)。代码第5处 strptime() 方法可能抛出 ValueError 异常。如果抛出异常,则会被代码第6处的 except 捕获。

            如果将 FileNotFoundError 和 OSError 捕获顺序调换,代码如下:

    1. def read_date_from_file(filename):
    2. try:
    3. file = open(filename)
    4. in_date = file.read()
    5. in_date = in_date.strip()
    6. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    7. return date
    8. except ValueError as e:
    9. print('处理 ValueError 异常')
    10. print(e)
    11. except OSError as e:
    12. print('处理 OSError 异常')
    13. print(e)
    14. except FileNotFoundError as e:
    15. print('处理 FileNotFountError 异常')
    16. print(e)

            那么 except FileNotFoundError as e 代码块永远不会进入,FileNotFoundError 异常代码永远不会被执行。因为 OSError 是 FileNotFoundError 父类,而 ValueError 异常与 OSError 和 FileNotFoundError 异常没有父子关系,捕获 ValueError 异常位置可以随意放置。

    4.3 try-except 语句嵌套

    Python 提供的 try-except 语句是可以任意嵌套的,修改 4.2 节示例代码如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_except3.py
    3. # try-except语句嵌套示例
    4. import datetime as dt
    5. def read_date_from_file(filename):
    6. try:
    7. file = open(filename)
    8. try: # --1
    9. in_date = file.read() # --2
    10. in_date = in_date.strip()
    11. date = dt.datetime.strptime(in_date, '%Y-%m-%d') # --3
    12. return date
    13. except ValueError as e: # --4
    14. print('处理 ValueError 异常')
    15. print(e)
    16. except FileNotFoundError as e: # --5
    17. print('处理 FileNotFoundError 异常')
    18. print(e)
    19. except OSError as e: # --6
    20. print('处理 OSError 异常')
    21. print(e)
    22. date = read_date_from_file('readme.txt')
    23. print('日期 = {0}'.format(date))

            上述代码第1~4处是捕获 ValueError 异常的 try-except 语句,可见这个 try-except 语句就嵌套在捕获 FileNotFoundError 和 OSError 异常的 try-except 语句内。

            程序执行时如果内层抛出异常,首先由内层的 except 进行捕获,如果捕获不到,则由外层的 except 捕获。例如,代码第 2 处的 read() 方法可能抛出 OSError 异常,该异常无法被内层 except 捕获,最后被代码第 6 处的外层 except 捕获。

    注意:try-except 代码块不仅可以嵌套在 try 代码块中,还可以嵌套在 except 代码块或 finally 代码块中。try-except 嵌套会使程序流程变得复杂,如果能用多 except 捕获的异常,尽量不要使用 try-except 嵌套。要梳理好程序的流程再考虑 try-except 嵌套的必要性。

    4.4 多重异常捕获

    多个 except 代码块客观上提高了程序的健壮性,但是也大大增加了程序代码量。有些异常虽然种类不同,但捕获之后的处理却是相同的,看如下代码:

    1. try:
    2. <可能会抛出异常的语句>
    3. except ValueError as e:
    4. <调用方法 method1 处理>
    5. except OSError as e:
    6. <调用方法 method1 处理>
    7. except FileNotFoundError as e:
    8. <调用方法 method1 处理>

            三个不同类型的异常,要求捕获之后的处理都是调用 method1 方法。是否可以把这些异常合并起来处理呢?Python 中可以把这些异常放在一个元组中,这就是多重异常捕获,可以帮助解决此类问题。上述代码修改如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_except4.py
    3. # try-except语句多重异常捕获示例
    4. import datetime as dt
    5. def read_date_from_file(filename):
    6. try:
    7. file = open(filename)
    8. in_date = file.read()
    9. in_date = in_date.strip()
    10. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    11. return date
    12. except (ValueError, OSError) as e: # --1
    13. print('调用方法 method1 异常处理...')
    14. print(e)
    15. date = read_date_from_file('readme.txt')
    16. print('日期 = {0}'.format(date))

    代码中 (ValueError, OSError) 就是多重异常捕获。

    注意:可能有人会问为什么不写成 (ValueError, FileNotFoundError, OSError) 呢?这是因为 FileNotFoundError 属于 OSError 异常,OSError 异常可以捕获它的所有子类异常了。

    五 异常堆栈跟踪

    有时从程序员的角度需要知道更加详细的异常信息时,可以打印堆栈跟踪信息。堆栈跟踪信息可以通过 Python 内置模块 traceback 提供的 print_exc() 函数实现,print_exc() 函数的语法格式如下:

    traceback.print_exc(limit=None, file=None, chain=True)

    其中,参数 limit 限制堆栈跟踪个数,默认 None 是不限制;参数 file 判断是否输出堆栈跟踪信息到文件,默认 None 是不输出到文件;参数 chain 为 True,则将 __cause____context__ 等属性串联起来,就像解释器本身打印未处理异常一样打印。

    堆栈跟踪示例代码如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/traceback_demo.py
    3. # 异常堆栈跟踪
    4. import datetime as dt
    5. import traceback as tb # --1
    6. def read_date_from_file(filename):
    7. try:
    8. file = open(filename)
    9. in_date = file.read()
    10. in_date = in_date.strip()
    11. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    12. return date
    13. except (ValueError, OSError) as e:
    14. print('调用方法 method1 异常处理...')
    15. tb.print_exc() # --2
    16. date = read_date_from_file('readme.txt')
    17. print('日期 = {0}'.format(date))

    上述代码第 2 处 tb.print_exc() 语句是打印异常堆栈信息,tb.print_exc() 函数使用了 traceback 模块,因此需要在文件开始导入 traceback 模块。

    发生异常输出结果如下:

    1. F:\python_work\异常处理>python traceback_demo.py
    2. 调用方法 method1 异常处理...
    3. Traceback (most recent call last):
    4. File "F:\python_work\异常处理\traceback_demo.py", line 13, in read_date_from_file
    5. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    6. File "D:\Program Files\Python\lib\_strptime.py", line 568, in _strptime_datetime
    7. tt, fraction, gmtoff_fraction = _strptime(data_string, format)
    8. File "D:\Program Files\Python\lib\_strptime.py", line 349, in _strptime
    9. raise ValueError("time data %r does not match format %r" %
    10. ValueError: time data '202b-10-30' does not match format '%Y-%m-%d'
    11. 日期 = None

    堆栈信息从上往下为程序执行过程中函数(或方法)的调用顺序,其中的每一条信息明确指出了哪一个文件、哪一行调用哪个函数或方法。程序员能够通过堆栈信息很快定位程序哪里出了问题。

    提示:在捕获到异常之后,通过 print_exc() 函数可以打印异常堆栈跟踪信息,往往只是用于调试,给程序员提示信息。堆栈跟踪信息对最终用户是没有意义的,本例中如果出现异常很有可能是用户输入的日期无效。捕获到异常之后应该给用户弹出一个对话框,提示用户输入日期无效,请用户重新输入,用户重新输入之后再重新调用上述函数。这才是捕获异常之后的正确处理方案。

    六 释放资源

            有时 try-except 语句会占用一些系统资源,如打开文件、网络连接、连接数据库和使用数据结果等,这些资源不能通过 Python 的垃圾回收机制回收,需要程序员自己手动释放。为了确保这些资源能够被释放,可以使用 finally 代码块或 with as 自动资源管理。

    6.1 finally 代码块

    try-except 语句后面还可以跟一个 finally 代码块,try-except-finally 语句语法如下:

    1. try:
    2. <可能会抛出异常的语句>
    3. except [异常类型1]:
    4. <处理异常>
    5. except [异常类型1]:
    6. <处理异常>
    7. ...
    8. except [异常类型n]:
    9. <处理异常>
    10. finally:
    11. <释放资源>

    无论 try 正常结束还是 except 异常结束都会执行 finally 代码块,如下图所示:

    图1  finally代码块流程

    实例:使用 finally 代码块示例代码如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_except_finally.py
    3. # 异常堆栈跟踪
    4. import datetime as dt
    5. def read_date_from_file(filename):
    6. try:
    7. file = open(filename)
    8. in_date = file.read()
    9. in_date = in_date.strip()
    10. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    11. return date
    12. except ValueError as e:
    13. print('处理 ValueError 异常')
    14. except FileNotFoundError as e:
    15. print('处理 FileNotFoundError 异常')
    16. except OSError as e:
    17. print('处理 OSError 异常')
    18. finally: # --1
    19. file.close() # --2
    20. date = read_date_from_file('readme.txt')
    21. print('日期 = {0}'.format(date))

    上述代码第1处是 finally 代码块,在这里通过关闭文件释放资源,即代码第2处:file.close() 的关闭文件语句。上述代码还是存在问题,如果在执行 open(filename) 打开文件时,即使抛出了异常也会执行 finally 代码块。执行 file.close() 关闭文件会抛出如下异常。

    1. F:\python_work\异常处理>python try_except_finally.py
    2. 处理 FileNotFoundError 异常
    3. Traceback (most recent call last):
    4. File "F:\python_work\异常处理\try_except_finally.py", line 23, in
    5. date = read_date_from_file('readme.txt')
    6. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    7. File "F:\python_work\异常处理\try_except_finally.py", line 21, in read_date_from_file
    8. file.close() # --2
    9. ^^^^
    10. UnboundLocalError: cannot access local variable 'file' where it is not associated with a value

    UnboundLocalError 异常是 NameError 异常的子类,异常信息提示没有找到 file 变量,这是因为 open(filename) 打开文件失败,所以 file 变量没有被创建。事实上 file.close() 关闭的前提是文件已经成功打开。为了解决此问题,可以使用 else 代码块。

    6.2 else 代码块

    与 while 和 for 循环类型,try 语句也可以带有 else 代码块,它是在程序正常结束时执行的代码块,程序流程如下图所示:

    图2  else 代码块流程

    将 6.1 节 的示例程序中存在的问题可以使用 else 代码块解决,修改 6.1 示例代码如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_except_else.py
    3. # 异常处理else代码块
    4. import datetime as dt
    5. def read_date_from_file(filename):
    6. try:
    7. file = open(filename) # --1
    8. except OSError as e:
    9. print('处理 OSError 异常')
    10. print('打开文件失败')
    11. else: # --2
    12. print('打开文件成功')
    13. try:
    14. in_date = file.read()
    15. in_date = in_date.strip()
    16. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    17. return date
    18. except ValueError as e:
    19. print('处理 ValueError 异常')
    20. except OSError as e:
    21. print('处理 OSError 异常')
    22. finally: # --3
    23. file.close()
    24. date = read_date_from_file('readme.txt')
    25. print('日期 = {0}'.format(date))

    上述代码中 open(filename) 语句单独放在一个 try 中,见代码第1处。如果正常打开这个文件,程序会执行 else 代码块,见代码第2处。else 代码块中嵌套了 try 语句,在这个 try 代码中读取文件内容和解析日期,最后在嵌套 try 对应的 finally 代码块中执行 file.close() 关闭文件。

    1)运行结果1:readme.txt 文件不存在时

    1. F:\python_work\异常处理>python try_except_else.py
    2. 处理 OSError 异常
    3. 打开文件失败
    4. 日期 = None

    2)运行结果2:readme.txt 文件存在,文件内容为: 2022-12-04

    1. F:\python_work\异常处理>python try_except_else.py
    2. 打开文件成功
    3. 日期 = 2022-12-04 00:00:00

    3)运行结果3:readme.txt 文件存在,文件内容为:2022-12-0B

    1. F:\python_work\异常处理>python try_except_else.py
    2. 打开文件成功
    3. 处理 ValueError 异常
    4. 日期 = None

    6.3 with as 代码块自动资源管理

    6.2 节的程序代码虽然 “健壮”,但是程序流程比较复杂,这样的程序代码难以维护。为此 Python 提供了一个 with as 代码块帮助自动释放资源,它可以替代 finally 代码块,优化代码结构,提供程序的可读性。with as 提供了一个代码块,在 as 后面声明一个资源变量,当 with as 代码块结束之后自动释放资源。

    实例:示例代码如下。

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_with_as.py
    3. # 异常处理 with as 代码块自动资源管理
    4. import datetime as dt
    5. def read_date_from_file(filename):
    6. try:
    7. with open(filename) as file: # --1
    8. in_date = file.read()
    9. in_date = in_date.strip()
    10. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    11. return date
    12. except ValueError as e:
    13. print('处理 ValueError 异常')
    14. except OSError as e:
    15. print('处理 OSError 异常')
    16. print('打开文件失败')
    17. date = read_date_from_file('readme.txt')
    18. print('日期 = {0}'.format(date))

    上述代码第1处是使用 with as 代码块,with 语句后面的 open(filename) 语句可以创建资源对象,然后赋值给 as 后面的 file 变量。在 with as 代码块中包含了资源对象相关代码。完成后自动释放。采用了自动资源管理后不再需要 finally 代码块,不需要自己释放这些资源。

    注意:所有可以自动管理的资源,需要实现上下文管理协议(Context Management Protocol)。

    示例运行结果:readme.txt 文件存在,文件内容为:2022-12-0B

    1. F:\python_work\异常处理>python try_with_as.py
    2. 处理 ValueError 异常
    3. 日期 = None

    七 自定义异常类

    有些公司为了提供代码的可重用性,自己开发了一些 Python 类库,其中有自己编写的一些异常类。实现自定义异常类需要继承 Exception 类或其子类。

    实现自定义异常类示例代码如下:

    1. # coding=utf-8
    2. # 代码文件: 异常处理/custom_except_class.py
    3. class MyException(Exception):
    4. def __init__(self, message): # --1
    5. super().__init__(message) # --2

    上述代码实现了自定义异常类,代码第1处是定义构造方法类,其中的参数 message 是异常描述信息。代码第2处 super().__init__(message) 是调用父类构造方法,并把参数 message 传入给父类构造方法。自定义异常就是这样简单,只需要提供一个字符串参数的构造方法就可以了。

    8 显式抛出异常

    上文中见到的异常都是由系统生成的,当异常抛出时,系统会创建一个异常对象,并将其抛出。但也可以通过 raise 语句显式抛出异常,语法格式如下:

    raise BaseException 或其子类的实例

    显式抛出异常的目的有很多,例如不想某些异常传给上层调用者,可以捕获之后重新显式抛出另一种异常给调用者。

    修改第 4 节示例代码如下:

    1. # coding=utf-8
    2. # 代码文件:异常处理/try_except_raise.py
    3. # 使用 raise 语句显示抛出异常
    4. import datetime as dt
    5. class MyException(Exception):
    6. def __init__(self, message):
    7. super().__init__(message)
    8. def read_date_from_file(filename):
    9. try:
    10. file = open(filename)
    11. in_date = file.read()
    12. in_date = in_date.strip()
    13. date = dt.datetime.strptime(in_date, '%Y-%m-%d')
    14. return date
    15. except ValueError as e:
    16. raise MyException('不是有效的日期') # --1
    17. except FileNotFoundError as e:
    18. raise MyException('文件找不到') # --2
    19. except OSError as e:
    20. raise MyException('文件无法打开或无法读取') # --3
    21. date = read_date_from_file('readme.txt')
    22. print('日期 = {0}'.format(date))

    如果软件设计者不希望 read_date_from_file() 中捕获的 ValueErrorFileNotFoundErrorOSError 异常出现在上层调用者中,那么可以在捕获到这些异常时,通过 raise 语句显式抛出一个异常,见代码第1、2 和 3 处显式抛出自定义的 MyException 异常。

    注意:raise 显式抛出的异常与系统生成并抛出的异常在处理方式上没有区别,就是两种方法,要么捕获后自己处理,要么抛出给上层调用者。

  • 相关阅读:
    企业架构LNMP学习笔记51
    python实现炫酷的屏幕保护程序
    【微信小程序】选择宝——选择困难症的拯救者
    计算机网络期末复习-Part2
    云服务器基本介绍
    数据库MySQL
    SpringBoot开启异步多线程
    校园图书馆自习室管理系统 毕业设计源码25035
    java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发
    2020 ICPC Shanghai Site G
  • 原文地址:https://blog.csdn.net/u010429831/article/details/127724109