一、bug的简介
1947年,世界第一部万用计算机的进化版——马克2号(Mark II
)的程序运行发生了异常,计算机程序之母格蕾丝经调试后,发现是一只飞蛾卡在了继电器触点中间,于是她把虫子的尸体
粘在了自己的工作日记上,称它们为臭虫(bug
)。
从此以后,计算机程序或硬件系统中存在的错误、故障、缺陷或漏洞,统称为bug
。调试程序的步骤,也称为debug
。
当bug
导致程序跳出运行时,系统会报出对应的异常类型,Python
中常见的异常有:语法格式错误SyntaxError
,值类型错误ValueError
,空索引错误IndexError
,空键错误KeyError
,未声明错误NameError
,未分配错误AttributeError
,除零错误ZeroDivisionError
,……
二、异常捕获预处理机制
1. 继承
Python
中,异常也是对象,所有内置的异常类都是从BaseException
类继承,且都在内建模块builtins
中定义。在捕获异常时,匹配的是其对应的异常类及其父类。继承层次如下:
BaseException # 所有异常的基类
+-- SystemExit # 解释器请求退出
+-- KeyboardInterrupt # 用户中断执行(Ctrl+C)
+-- GeneratorExit # 生成器异常退出
+-- Exception # 常规异常的基类
+-- StopIteration # 迭代器没有更多的值
+-- StopAsyncIteration # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
+-- ArithmeticError # 算术错误异常的基类
| +-- FloatingPointError # 浮点计算错误
| +-- OverflowError # 溢位错误(数值太大)
| +-- ZeroDivisionError # 除(模)零
+-- AssertionError # 断言失败
+-- AttributeError # 属性/方法的引用或赋值失败
+-- BufferError # 缓冲区错误
+-- EOFError # 当input()函数在未读取任何数据时达到文件结束条件(EOF)时引发
+-- ImportError # 导入模块/对象失败
| +-- ModuleNotFoundError # 寻找模块失败
+-- LookupError # 无效键/索引时的基类
| +-- IndexError # 序列无此索引
| +-- KeyError # 映射无此键
+-- MemoryError # 内存溢出错误
+-- NameError # 对象未声明/初始化
| +-- UnboundLocalError # 本地变量未初始化
+-- OSError # 操作系统错误
| +-- BlockingIOError # 操作将阻塞对象(e.g.socket)设置为非阻塞操作
| +-- ChildProcessError # 子进程操作失败
| +-- ConnectionError # 连接异常的基类
| | +-- BrokenPipeError # 在已关闭的管道写入
| | +-- ConnectionAbortedError # 连接尝试被对等方中止
| | +-- ConnectionRefusedError # 连接尝试被对等方拒绝
| | +-- ConnectionResetError # 连接由对等方重置
| +-- FileExistsError # 创建已存在的文件或目录
| +-- FileNotFoundError # 请求不存在的文件或目录
| +-- InterruptedError # 系统调用被输入信号中断
| +-- IsADirectoryError # 在目录上请求文件操作(e.g.os.remove())
| +-- NotADirectoryError # 在非目录对象上请求目录操作(e.g.os.listdir())
| +-- PermissionError # 操作权限不足
| +-- ProcessLookupError # 给定进程不存在
| +-- TimeoutError # 系统函数在系统级别超时
+-- ReferenceError # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
+-- RuntimeError # 未定义类错误
| +-- NotImplementedError # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍需添加实际实现
| +-- RecursionError # 超出最大递归深度
+-- SyntaxError # 语法格式错误
| +-- IndentationError # 缩进错误
| +-- TabError # Tab和空格混用
+-- SystemError # 解释器内部错误
+-- TypeError # 操作或函数应用于不适当类型的对象
+-- ValueError # 操作或函数接收到具有正确类型但值不合适的参数
| +-- UnicodeError # Unicode错误
| +-- UnicodeDecodeError # Unicode解码错误
| +-- UnicodeEncodeError # Unicode编码错误
| +-- UnicodeTranslateError # Unicode转码错误
+-- Warning # 警告的基类
+-- DeprecationWarning # 已弃用功能警告
+-- PendingDeprecationWarning # 不推荐使用功能警告
+-- RuntimeWarning # 可疑行为警告
+-- SyntaxWarning # 可疑语法警告
+-- UserWarning # 用户代码生成警告
+-- FutureWarning # 可能错误警告
+-- ImportWarning # 模块导入警告
+-- UnicodeWarning # Unicode警告
+-- BytesWarning # 与bytes和bytearray相关的警告
+-- ResourceWarning # 资源使用警告(被默认警告过滤器忽略)
2. 捕获与预处理
try...except...
语句用于捕获并处理异常,其机制为:
try
下的代码块运行时,如果发生异常,不跳出程序,从上到下依次检索except
子句,并只执行第一个匹配该异常的except
子句。
try:
n = 1/0 # 发生错误,转到except语句
print(n) # 不执行
except SyntaxError or ValueError: # 异常类型错误
print('Error') # 不执行
except ZeroDivisionError: # 异常类型正确
print('ZeroDivisionError') # 打印错误
若需要对其他所有错误有统一的应对行为,可不设置异常类型,或者将except
子句匹配的异常类型设为BaseException
。此外,也可以通过as
将匹配的异常赋给变量,以便于查询异常发生的具体原因,并防止 “异常字句过于宽泛” 的警告。
try:
n = m
print(n)
# 一般是最后一个
except BaseException as E: # except:(必须是最后一个)
print(E) # name 'm' is not defined
3. 嵌套与传递
try
块可以在许多结构中嵌套,包括自身、循环结构和函数体等等。在多层嵌套中,异常的传递机制为:
若异常未被内层的except
子句捕获,则将被递交到外层的try
块,直至异常被处理。如果都不处理该异常,就终止其所在线程。
在此基础上,还有追加子句else
和finally
。当没有匹配的except
子句时,异常就会由else
子句捕获,并执行else
子块。而不论try
块中是否发生异常,finally
子句最终都会被执行,并释放本层try
块的资源。
# 载入精确计算库
from decimal import Decimal
# 初始化循环条件
flag = 1
while flag:
# 运算器主体
try:
# 功能说明
print('保留三位小数的除法运算器'.center(20))
# 输入参数
n = input('被除数:')
m = input('除数:')
# 计算结果
result = '商:{:.3f}'.format(float(Decimal(n) / Decimal(m)))
# 除零错误
except ZeroDivisionError:
print('division by zero')
# 输入类型错误
except ArithmeticError:
print('invalid operand type(s)')
# 无异常,输出结果
else:
print(result)
# 循环控制
finally:
try:
flag = int(input('输入1继续,输入0退出:'))
except BaseException:
flag = 0
以上实例代码实现了一个保留三位小数的除法运算器。其中,finally
子句中嵌套了一个内层try
块。另外,由于调用了第三方库,大部分发生的相似错误并不属于常规异常子类,可以考虑向其父类追溯。
特别的,循环结构或函数体中的内层try
块中的finally
子句里,若含有break
或return
语句,那么异常不再发生传递,并且其所在进程将被终止。