第八章 异常机制
在本章, 我们首先会了解什么是异常: 软件程序在运行过程中,可能会遇到能使其不能正常运行的问题,我们称之为异常,英文是: Exception 然后, 我们会了解捕获异常的四种结构方式, 再然后, 我们梳理常见放的异常 再然后, 我们补充其他关于异常相关的问题, 最后, 我们通过使用 Pycharm 来体会异常的调试过程
程序在运行过程中发生的意外情况,称为异常, 程序运行时一旦出现了异常,将会导致程序立即终止,异常之后的代码将无法继续执行,所以需要对异常进行处理
异常机制本质:
python中,引进了很多用来描述和处理异常的类,称为异常类. 异常类定义中包含了该类异常的信息和对异常进行处理的方法.
下面较为完整的展示了python中内建异常类的继承层次:
异常解决的关键:定位 当发生异常时,解释器会报相关的错误信息,并会在控制台打印出相关错误信息. 我们只需按照从上到下的顺序即可追溯(Trackback)错误发生的过程,最终定位引起错误的哪一行代码.
实操代码
# 测试简单的0不能做除数异常
# 因为如果假设成立, 则说明 3/0=0 => 可以推导 0*0=3, 因为结果不成立, 因此假设不成立
# a = 3/0
def a():
print("run in a() start! ")
num = 1/0
print("run in a() end! ")
def b():
print("run in b() start! ")
a()
print("run in b() end! ")
def c():
print("run in c() start! ")
b()
print("run in c() end! ")
print("step1")
c()
print("step2")
结果输出
从打印输出结果来看, 最底层的方法(eg: a())出错之后, 会在上层调用的方法位置处抛出异常. 因为错误信息以栈的形式输出, 因此最顶层的调用因为先打印, 所以会先被我们看到. 因此对底层/最有用的信息一般位于最下面.
这种结构是最常见, 也是最常用的结构
语法结构
try:
被监控的可能引发异常的语句块
except BaseException [as e]:
异常处理语句块
注意事项:
实操代码
def a():
print("run in a() start! ")
try:
num = 1 / 0
except BaseException as e:
print("捕获异常之后执行此处代码")
print("run in a() end! ")
def b():
print("run in b() start! ")
a()
print("run in b() end! ")
def c():
print("run in c() start! ")
b()
print("run in c() end! ")
print("step1")
c()
print("step2")
结果输出
try…except 的结构可以捕获所有的异常,工作中也很常见. 但是,一般建议尽量捕获可能出现的多个异常(按照先子类后父类的顺序),并且针对性写出异常处理代码 为了避免遗漏可能出现的异常,可以在最后增加 BaseException 。结构如下
语法结构
try:
被监控的、可能引发异常的语句块
except Exception1:
处理Exception1的语句块
except Exception2:
处理Exception2的语句块
[...]
except BaseException:
处理可能遗漏的异常的语句块
实操代码
try:
a = input("请输入被除数: ")
b = input("请输入除数: ")
result = float(a)/float(b)
print(result)
except ZeroDivisionError:
print("异常: 0不能做除数")
except ValueError:
print("异常: 输入的必须是数值类型!")
except BaseException as e:
print(e)
print(type(e))
结果输出
在 try…except…else 结构的基础上增加了 else 块 . 如果 try 块中没有抛出异常,则执行else 块. 如果 try 块中抛出异常,则执行 except 块,不执行 else 块.
语法结构
try:
被监控的可能引发异常的语句块
except BaseException [as e]:
异常处理语句块
else:
没有抛出异常时执行的语句块
实操代码
try:
a = input("请输入被除数: ")
b = input("请输入除数: ")
result = float(a)/float(b)
except BaseException as e:
print(e)
else:
print("两数相除, 结果是:", result)
结果输出
try…except…finally 结构中, finally 块无论是否发生异常都会被执行, 通常用来释放 try 块中申请的资源
语法结构
try:
被监控的可能引发异常的语句块
except BaseException [as e]:
异常处理语句块
finally:
无论是否捕获异常都会执行的语句块
实操代码
try:
a = input("请输入被除数: ")
b = input("请输入除数: ")
result = float(a)/float(b)
except BaseException as e:
print(e)
else:
print("两数相除, 结果是:", result)
finally:
print("我是finally中的语句, 无论发生异常与否, 都执行!")
输出结果
实操代码2
try:
f = open("d:/a.txt",'r')
content = f.readline()
print(content)
except BaseException as e:
print(e)
finally:
f.close() #释放资源. 此处也可能会发生异常。若发生异常,则程序终止,不会继续往下执行
print("step4")
# Python中的异常都派生自 BaseException 类, 我们测试和列出常见的一些异常, 方便初学者掌握
# 1. SyntaxError :语法错误 | SyntaxError: invalid syntax. Perhaps you forgot a comma?
# int a =3
# 2. NameError :尝试访问一个没有申明的变量 | NameError: name 'a' is not defined
# print(a)
# 3. ZeroDivisionError :除数为0错误(零除错误) | ZeroDivisionError: division by zero
# a = 3 / 0
# 4. ValueError :数值错误 | ValueError: could not convert string to float: 'skyII'
# float("skyII")
# 5. TypeError :类型错误 | TypeError: unsupported operand type(s) for +: 'int' and 'str'
# 123+"abc"
# 6. AttributeError :访问对象的不存在的属性 | AttributeError: 'int' object has no attribute 'sayhi'
# a = 100
# a.sayhi()
# 7. IndexError :索引越界异常 | IndexError: list index out of range
# a = [4, 5, 6]
# a[10]
# 8. KeyError :字典的关键字不存在 | KeyError: 'salary'
a = {'name': "sari", 'age': 18}
a['salary']
由于 return 有两种作用:结束方法运行、返回值. 我们一般不把 return放到异常处理结构中,而是放到方法最后.
实操代码
一般不要将return语句放到try、except、else、finally块中, 会发生一些意想不到的错误. 建议放到方法最后 如上面代码, 这种写法就会导致无论是否正确, 都会导致 try内的return无法执行而去执行 finally 内的语句
def testException():
try:
a = input("请输入被除数: ")
b = input("请输入除数: ")
result = float(a) / float(b)
return result
except BaseException as e:
print(e)
return "a"
finally:
print("我是finally中的语句, 无论发生异常与否, 都执行!")
return "b"
# return "b" # 正常情况应该放到这里
print(testException())
结果输出
finally 块由于是否发生异常都会执行,通常我们作为放释放资源的代码. 其实,我们可以通过 with 上下文管理,更方便的实现释放资源的操作. with 上下文管理可以自动管理资源,在 with 代码块执行完毕后自动还原进入该代码之前的现场或上下文. 不论何种原因跳出 with块,不论是否有异常,总能保证资源正常释放. 极大的简化了工作,在文件操作、网络通信相关的场合非常常用.
语法结构
with context_expr [ as var]:
语句块
实操代码
# 【示例】 with 上下文管理文件操作
with open("d:/dd.txt") as f:
for line in f:
print(line)
我们可以利用
traceback.print_exc(file=f)
, 将异常日志输出到日志文件中
# traceback模块和生成异常日志
import traceback
try:
print("step1")
num = 1 / 0
except:
# traceback.print_exc() # 直接输出错误调用记录
with open("d:/a.log", "a") as f: # 使用 traceback 将异常信息写入日志文件
traceback.print_exc(file=f)
程序开发中,有时候我们也需要自己定义异常类. 自定义异常类一般都是运行时异常,通常继承 Exception 或其子类即可. 命名一般以Error 、 Exception 为后缀. 自定义异常由 raise 语句主动抛出.
实操代码
# 【示例】自定义异常类和raise语句
class AgeError(Exception):
def __init__(self, errorInfo):
Exception.__init__(self)
self.errorInfo = errorInfo
def __str__(self):
return str(self.errorInfo) + ", 年龄错误! 应该在 1-150之间!"
# 测试代码
if __name__ == "__main__": # 如果为True, 则模块是作为独立文件运行, 可以执行测试代码
age = int(input("输入一个年龄: "))
if age < 1 or age > 150:
raise AgeError(age)
else:
print("正常的年龄: ", age)
结果输出
程序运行到此处,暂时挂起,停止执行。我们可以详细在此时观察程序的运行情况,方便做出进一步的判断
调试步骤
单击工具栏上的按钮
/ 右键单击编辑区,点击: debug ‘模块名’
/ 快捷键: shift+F9
我们通过上图中的按钮进行调试操作,它们的含义如下: