Python 异常
1. 异常的捕获
2. 异常的传递
3. 自定义异常
4. 异常中抛出异常
1. 异常的捕获
什么是异常?
当 Python 检测到一个错误时,解释器就无法继续执行了,并且会出现一些错误的提示,这就是所谓的"异常"。
基本语法:
try: # 可能产生异常的代码,放在try中 except IOError: # 需要捕获的异常类型,如IOError # 产生异常时的处理方法
示例:捕获多种类型的异常
1 try: 2 print('-----test--1---') 3 open('123.txt', 'r') # 如果 123.txt 文件不存在,那么会产生 IOError 异常 4 print('-----test--2---') 5 print(num) # 如果num变量没有定义,那么会产生 NameError 异常 6 7 # 如果想通过一次except捕获到多个异常可以用一个元组的方式 8 except (IOError, NameError) as errorMsg: 9 # errorMsg 里会保存捕获到的错误信息 10 print(errorMsg)
示例:捕获所有类型的异常
1 try: 2 print('-----test--1---') 3 open('123.txt', 'w') # 如果 123.txt 文件不存在,那么会产生 IOError 异常 4 print('-----test--2---') 5 print(num) # 如果num变量没有定义,那么会产生 NameError 异常 6 7 # 捕获所有类型的异常并打印错误信息 8 except Exception as errorMsg: 9 # 若不需要打印具体错误信息则可仅使用except: 10 11 # errorMsg 里会保存捕获到的错误信息 12 print(errorMsg)
else
在 if 中,它的作用是当条件不满足时执行。
同样在 try...except... 中也是如此,即如果没有捕获到异常,那么就执行 else 中的事情。
1 try: 2 num = 100 3 print num 4 except NameError as errorMsg: 5 print('产生错误了:%s' % errorMsg) 6 else: 7 print('没有捕获到异常')
finally
try...finally... 语句用来表达这样的情况:
在程序中,如果有一段代码必须要执行,即无论异常是否产生都要执行,那么此时就需要使用 finally,比如文件关闭,释放锁,把数据库连接返还给连接池等。
注意:
- 其实不加finally也不影响程序逻辑,其意义是可读性和完整性。
- 如果try块中执行到return时并不会直接返回结果,而是先去执行finally块,再返回先前的值,除非finally中有return,finally中的return会破坏程序的完整性且抑制方法产生的异常,不要使用。
示例:
1 try: 2 f = open("test.txt") 3 try: 4 while 1: 5 content = f.readline() 6 if len(content) == 0: 7 break 8 print(content) 9 except: 10 pass 11 finally: 12 f.close() 13 print("关闭文件") 14 except: 15 print("没有这个文件")
获取具体的错误代码行及错误信息
1 import traceback 2 3 exception_info = "" 4 print("start: ") 5 try: 6 print(1/0) # 注释 7 except: 8 traceback.print_exc() # 将具体的错误代码行及错误信息打印到屏幕 9 exception_info = traceback.format_exc() # 将具体的错误代码行及错误信息存储到变量中 10 print("end: ") 11 print(exception_info)
执行结果:
start: Traceback (most recent call last): File "code.txt", line 6, in <module> print(1/0) # 注释 ZeroDivisionError: division by zero end: Traceback (most recent call last): File "code.txt", line 6, in <module> print(1/0) # 注释 ZeroDivisionError: division by zero
2. 异常的传递
try 嵌套
在 try 嵌套中,如果里层的 try 没有捕获到这个异常,那么外层的 try 会接收到这个异常,然后进行处理,如果外层的 try 依然没有捕获到,那么再进行传递给外外层,以此类推。
1 try: 2 f = open("test.txt") 3 try: 4 while 1: 5 content = f.readline() 6 if len(content) == 0: 7 break 8 print("文件内容为:{}".format(content)) 9 num 10 finally: 11 f.close() 12 print("关闭文件") 13 except: 14 print("没有这个文件") 15 finally: 16 print("外层的finally")
执行结果:
文件内容为:Hello world.
关闭文件
没有这个文件
外层的finally
函数嵌套调用
- 如果一个异常是在一个函数中产生的,例如函数A调用函数B,函数B调用函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行,如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,即报错。
- 注意观察以下结果,当调用test3函数时,在test1函数内部产生了异常,此异常被传递到test3函数中完成了异常处理,而当异常处理完后,并没有返回到函数test1中进行执行,而是在函数test3中继续执行。
1 def test1(): 2 print("1-1") 3 print(num) 4 print("1-2") 5 6 7 # 内部调用函数1,无异常处理机制 8 def test2(): 9 print("2-1") 10 test1() 11 print("2-2") 12 13 # 内部调用函数1,有异常处理机制 14 def test3(): 15 try: 16 print("3-1") 17 test1() 18 print("3-2") 19 except Exception as msg: 20 print("捕获到了异常,内容为:{}".format(msg)) 21 print("3-3") 22 23 24 25 test3() 26 print("-----分割线-----") 27 test2()
执行结果:
3-1 1-1 捕获到了异常,内容为:name 'num' is not defined 3-3 -----分割线----- 2-1 1-1 Traceback (most recent call last): File "test.py", line 27, in <module> test2() File "test.py", line 10, in test2 test1() File "test.py", line 3, in test1 print(num) NameError: name 'num' is not defined
3. 自定义异常
可以用raise语句来引发一个自定义的异常。异常/错误对象必须有一个名字,且它们应该是Error或Exception类的子类。
示例:引发自定义异常
1 class ShortInputException(Exception): 2 "自定义异常类" 3 4 def __init__(self, length, atlease): 5 super().__init__() 6 self.length = length 7 self.atlease = atlease 8 9 10 def main(): 11 try: 12 s = input("请输入:") 13 if len(s) < 3: 14 # 引发一个自定义异常 15 raise ShortInputException(len(s), 3) 16 # result这个变量被绑定到了错误的实例 17 except ShortInputException as result: 18 print("ShortInputException:输入的长度是%d,至少应是%d" % (result.length, result.atlease)) 19 else: 20 print("没有异常") 21 22 23 main()
执行结果:
请输入:ab
ShortInputException:输入的长度是2,至少应是3
以上程序中,关于代码 super().__init__() 的说明:
- 这一行代码,可以调用也可以不调用,建议调用,因为__init__()方法往往是用来对创建完的对象进行初始化工作,如果在子类中重写了父类的__init__()方法,意味着父类中的很多初始化工作没有做,这样就不能保证程序的稳定性。
- 所以在以后的开发中,如果重写了父类的__init__()方法,最好是先调用父类的这个方法,然后再添加自己的功能。
4. 异常中抛出异常
1 class Test: 2 3 def __init__(self, switch): 4 self.switch = switch # 是否捕获异常的开关 5 6 def calculate(self, a, b): 7 try: 8 return a / b 9 except Exception as result: 10 # 如果捕获异常的开关为开,则打印捕获信息 11 if self.switch: 12 print(result) 13 # 否则,抛出异常(抛给外层异常处理机制) 14 else: 15 raise 16 17 18 t1 = Test(1) # 打开开关 19 t1.calculate(1, 0) 20 print("-------分割线--------") 21 t2 = Test(0) # 关闭开关 22 t2.calculate(1, 0)
执行结果:
division by zero -------分割线-------- Traceback (most recent call last): File "test.py", line 22, in <module> t2.calculate(1, 0) File "test.py", line 8, in calculate return a / b ZeroDivisionError: division by zero