第 9 章 异常处理及程序调试
9.1 异常概述
在程序运行过程中, 经常会遇到各种各样的错误, 这些错误统称为 "异常". 这些异常有的是由于开发者将关键字敲错导致的, 这类错误多数产生的是 SyntaxError:invalid syntax(无效的语法), 这将直接导致程序不能运行. 这类异常是显式的, 在开发阶段很容易被发现. 还有一类是隐式的, 通常和使用者的操作有关.
实例 01: 模拟幼儿园分苹果
- def division():
- '''功能: 分苹果'''
- print("\n===================== 分苹果 ======================\n")
- apple = int(input("请输入苹果的个数:")) # 输入苹果的数量
- children = int(input("请输入来了几个小朋友:"))
- result = apple//children # 计算每人分几个苹果
- remain = apple-result*children # 计算余下几个苹果
- if remain>0:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个, 剩下",remain,"个.")
- else:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个.")
- if __name__ == '__main__':
- division() # 调用分苹果的函数
运行程序, 当输入苹果和小朋友的数量都是 10 时:
请输入苹果的个数: 10
请输入来了几个小朋友: 10
10 个苹果, 平均分给 10 个小朋友, 每人分 1 个.
如果在输入数量时, 不小心把小朋友的人数输成了 0:
产生了 ZeroDivisionError(除数为 0 错误) 的根源在于算术表达式 "10/0" 中, 0 作为除数出现, 所以正在执行的程序中被中断 (第 6 行以后, 包括第 6 行的代码都不会被执行).
Python 中还有很多异常.
异常 | 描述 |
NameError | 尝试访问一个没有声明的变量引发的错误 |
IndexError | 索引超出序列范围引发的错误 |
IndentationError | 缩进错误 |
ValueError | 传入错误 |
KeyError | 请求一个不存在的字典关键字引发的错误 |
IOError | 输入输出错误(如要读取的文件不存在) |
ImportError | 当 import 语句无法找到模块或 from 无法在模块中找到相应的名称时引发的错误 |
AttributeError | 尝试访问未知的对象属性引发的错误 |
TypeError | 类型不合适引发的错误 |
MemoryError | 内存不足 |
ZeroDivisionError | 除数为 0 引发的错误 |
9.2 异常处理语句
在程序开发时, 有些错误并不是每次运行都会出现.
下面将详细介绍 Python 中提供的异常处理语句.
9.2.1 try...eccept
在 Python 中, 提供了 try...except 语句捕获并处理异常. 在使用时, 把可能产生异常的代码放在 try 语句块中, 把处理结果放在 except 语句块中, 这样, 当 try 语句块中的代码出现错误时, 就会执行 except 语句块中的代码, 如果 try 语句块中的代码没有错误, 那么 except 语句块将不会执行. 具体的语法格式如下:
- try:
- block1
- except [ExceptionName [as alias]]:
- block2
参数说明:
block1: 表示可能出现错误的代码块.
ExceptionName [as alias]: 可选参数, 用于指定要捕获的异常. 其中, ExceptionName 表示要捕获的异常名称, 如果在其右侧加上 as alias, 则表示为当前的异常指定一个别名, 通过该别名, 可以记录异常的具体内容.
说明: 在使用 try...except 语句捕获异常时, 如果在 except 后面不指定异常名称, 则表示捕获全部异常.
block2: 表示进行异常处理的代码块. 在这里可以输出固定的提示信息, 也可以通过别名输出异常的具体内容.
说明: 使用 try...except 语句捕获异常后, 当程序出错时, 输出错误信息后, 程序会继续执行.
实例 02: 模拟幼儿园分苹果 (除数不能为 0)
对 "if __name__ =='__main__':" 语句下面的代码进行修改, 应用 try...except 语句捕获执行 division() 函数可能抛出的 ZeroDivisionError(除数为零) 异常, 修改后的代码如下:
- def division():
- '''功能: 分苹果'''
- print("\n===================== 分苹果 ======================\n")
- apple = int(input("请输入苹果的个数:")) # 输入苹果的数量
- children = int(input("请输入来了几个小朋友:"))
- result = apple//children # 计算每人分几个苹果
- remain = apple-result*children # 计算余下几个苹果
- if remain>0:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个, 剩下",remain,"个.")
- else:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个.")
- if __name__ == '__main__':
- try: # 捕获异常
- division() # 调用分苹果的函数
- except ZeroDivisionError: # 处理异常
- print("\n 出错了 ~_~ -- 苹果不能被 0 个小朋友分!")
执行以上的代码, 输入苹果的数量为 10, 小朋友的人数为 0 时, 将不再抛出异常:
目前, 我们只处理了除数为 0 的情况, 如果将苹果和小朋友的数量输入成小数或者不是数字会是什么结果呢?
可以看出, 程序中要求输入整数, 而实际输入的是小数, 则抛出 ValueError(传入的值错误) 异常. 要解决该问题, 可以在实例 02 的代码中, 为 try...except 语句再添加一个 except 语句, 用于处理抛出 ValueError 异常的情况. 修改后的代码如下:
- def division():
- '''功能: 分苹果'''
- print("\n===================== 分苹果 ======================\n")
- apple = int(input("请输入苹果的个数:")) # 输入苹果的数量
- children = int(input("请输入来了几个小朋友:"))
- result = apple//children # 计算每人分几个苹果
- remain = apple-result*children # 计算余下几个苹果
- if remain>0:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个, 剩下",remain,"个.")
- else:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个.")
- if __name__ == '__main__':
- try: # 捕获异常
- division() # 调用分苹果的函数
- except ZeroDivisionError: # 处理异常
- print("\n 出错了 ~_~ -- 苹果不能被 0 个小朋友分!")
- except ValueError as e: # 处理 ValueError 异常
- print("输入错误:",e) # 输出错误原因
再次运行程序:
多学两招:
在捕获异常时, 如果需要同时处理多个异常也可以采用下面的代码实现:
- try: # 捕获异常
- division() # 调用分苹果的函数
- except (ZeroDivisionError,ValueError) as e: # 处理异常
- print("输入错误:",e) # 输出错误原因
即在 except 语句后面使用一对小括号将可能出现的异常名称括起来, 多个异常名称之间使用逗号分隔. 如果想要显示具体的出错原因, 那么再加上 as 指定一个别名.
9.2.2 try...except...else 语句
在 Python 中国, 还有另一种异常处理结构, 它是 try...except...else 语句, 也就是在原来 try...except 语句的基础上再添加一个 else 子句, 用于指定当 try 语句块中没有发现异常时要执行的语句块. 该语句块中的内容当 try 语句中发现异常, 将不被执行. 例如, 实例 02 进行修改, 实现当 division() 函数被执行后没有抛出异常时, 输出文字 "分苹果顺利完成...". 修改后的代码如下:
- def division():
- '''功能: 分苹果'''
- print("\n===================== 分苹果 ======================\n")
- apple = int(input("请输入苹果的个数:")) # 输入苹果的数量
- children = int(input("请输入来了几个小朋友:"))
- result = apple//children # 计算每人分几个苹果
- remain = apple-result*children # 计算余下几个苹果
- if remain>0:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个, 剩下",remain,"个.")
- else:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个.")
- if __name__ == '__main__':
- try: # 捕获异常
- division() # 调用分苹果的函数
- except ZeroDivisionError : # 处理异常
- print("\n 出错了 ~_~ -- 苹果不能被 0 个小朋友分!")
- except ValueError as e: # 处理 ValueError 异常
- print("输入错误:",e) # 输出错误原因
- else: # 没有抛出异常时执行
- print("分苹果顺利完成...")
执行以上代码:
9.2.3 try...except...finally 语句
完整的异常处理语句应该包含 finally 代码块, 通常情况下, 无论程序中有无异常产生, finally 代码块中的代码都会被执行, 其语法格式如下:
- try:
- block1
- except [ExceptionName [as alias]]:
- block2
- fianlly:
- block3
对于 try...except...finally 语句的理解并不复杂, 它只是比 try...except 语句多了一个 finally 语句, 如果程序中有一些在任何情形中都必须执行的代码, 那么就可以将它们放在 finally 代码块中.
说明: 使用 except 子句是为了允许处理异常. 无论是否引起了异常, 使用 finally 子句都是可以执行清理代码. 如果分配了有限的资源 (如打开文件), 则应将释放这些资源的代码放置在 finally 代码块中.
例如, 在对实例 02 进行修改, 实现当 division() 函数在执行时无论是否抛出异常, 都输出文字 "进行了一次分苹果操作". 修改后的代码如下:
- def division():
- '''功能: 分苹果'''
- print("\n===================== 分苹果 ======================\n")
- apple = int(input("请输入苹果的个数:")) # 输入苹果的数量
- children = int(input("请输入来了几个小朋友:"))
- result = apple//children # 计算每人分几个苹果
- remain = apple-result*children # 计算余下几个苹果
- if remain>0:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个, 剩下",remain,"个.")
- else:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个.")
- if __name__ == '__main__':
- try: # 捕获异常
- division() # 调用分苹果的函数
- except ZeroDivisionError : # 处理异常
- print("\n 出错了 ~_~ -- 苹果不能被 0 个小朋友分!")
- except ValueError as e: # 处理 ValueError 异常
- print("输入错误:",e) # 输出错误原因
- else: # 没有抛出异常时执行
- print("分苹果顺利完成...")
- finally: # 无论是否抛出异常都执行
- print("进行了一次分苹果操作.")
执行程序:
至此, 已经介绍了异常处理语句的 try....except,try...except...else 和 try...except...finally 等形式.
异常处理语句的不同子句的执行关系
9.2.4 使用 raise 语句抛出异常
如果某个函数或方法可能会产生异常, 但不想在当前函数或方法中处理这个异常, 则可以使用 raise 语句在函数或方法中抛出异常. raise 语句的语法格式如下:
raise [ExceptionName[(reason)]]
其中, ExceptionName[(reason)] 为可选参数, 用于指定抛出的异常名称以及异常信息的相关描述. 如果省略, 就会把当前的错误原样抛出.
说明: ExceptionName[(reason)] 参数中的 "(reason)" 也可以省略, 如果省略, 则在抛出异常时, 不附带任何描述信息.
例如, 修改实例 02, 加入限制苹果数量必须大于或等于小朋友的数量, 从而保证每个小朋友都能至少分到一个苹果.
实例 03: 模拟幼儿园分苹果 (每个人至少分到一个苹果)
在第 5 行代码 "children = int(input(" 请输入来了几个小朋友:"))" 的下方添加一个 if 语句, 实现当苹果的数量小于小朋友的数量时, 应用 raise 语句抛出一个 ValueError 异常, 接下来再在最后一行语句的下方添加 except 语句处理 ValueError 异常.
- def division():
- '''功能: 分苹果'''
- print("\n===================== 分苹果 ======================\n")
- apple = int(input("请输入苹果的个数:")) # 输入苹果的数量
- children = int(input("请输入来了几个小朋友:"))
- if apple <children:
- raise ValueError("苹果太少了, 不够分...")
- result = apple//children # 计算每人分几个苹果
- remain = apple - result*children # 计算余下几个苹果
- if remain>0:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个, 剩下",remain,"个.")
- else:
- print(apple,"个苹果, 平均分给",children,"个小朋友, 每人分",result,"个.")
- if __name__ == '__main__':
- try: # 捕获异常
- division() # 调用分苹果的函数
- except ZeroDivisionError : # 处理 ZeroDivisionError 异常
- print("\n 出错了 ~_~ -- 苹果不能被 0 个小朋友分!")
- except ValueError as e: # ValueError
- print("\n 出错了~_~ --",e)
执行程序:
说明: 在应用 raise 抛出异常时, 要尽量选择合理的异常对象, 而不应该抛出一个与实际内容不相关的异常. 流入, 在实例 03 中, 想要处理的是一个和值有关的异常, 这时就不应该抛出一个 IndentationError 异常.
9.3 程序调试
来源: https://www.cnblogs.com/ooo888ooo/p/10360656.html