总的来说,编写程序时遇到的错误可大致分为 2 类,分别为语法错误和运行时错误。
>>> print'aaa' File "", line 1 print'aaa' ^ SyntaxError: invalid syntax #语法错误 >>> a = 1/0 Traceback (most recent call last): File " ", line 1, in ZeroDivisionError: division by zero #运行时错误 >>>
异常类型 | 含义 | 实例 |
---|---|---|
AssertionError | 当 assert 关键字后的条件为假时,程序运行会停止并抛出 AssertionError 异常 | >>> demo_list = ['C语言中文网'] >>> assert len(demo_list) > 0 >>> demo_list.pop() 'C语言中文网' >>> assert len(demo_list) > 0 Traceback (most recent call last): File " assert len(demo_list) > 0 AssertionError |
AttributeError | 当试图访问的对象属性不存在时抛出的异常 | >>> demo_list = ['C语言中文网'] >>> demo_list.len Traceback (most recent call last): File " demo_list.len AttributeError: 'list' object has no attribute 'len' |
IndexError | 索引超出序列范围会引发此异常 | >>> demo_list = ['C语言中文网'] >>> demo_list[3] Traceback (most recent call last): File " demo_list[3] IndexError: list index out of range |
KeyError | 字典中查找一个不存在的关键字时引发此异常 | >>> demo_dict={'C语言中文网':"c.biancheng.net"} >>> demo_dict["C语言"] Traceback (most recent call last): File " demo_dict["C语言"] KeyError: 'C语言' |
NameError | 尝试访问一个未声明的变量时,引发此异常 | >>> C语言中文网 Traceback (most recent call last): File " C语言中文网 NameError: name 'C语言中文网' is not defined |
TypeError | 不同类型数据之间的无效操作 | >>> 1+'C语言中文网' Traceback (most recent call last): File " 1+'C语言中文网' TypeError: unsupported operand type(s) for +: 'int' and 'str' |
ZeroDivisionError | 除法运算中除数为 0 引发此异常 | >>> a = 1/0 Traceback (most recent call last): File " a = 1/0 ZeroDivisionError: division by zero |
提示:表中的异常类型不需要记住,只需简单了解即可。
Python try except异常处理详解
Python 提供了try except语句捕获并处理异常,该异常处理语句的基本语法结构如下: try: 可能产生异常的代码块 except [(Error1, Error2, ...) [as e]]: 处理异常的代码块1 except [(Error3, Error4, ...) [as e]]: 处理异常的代码块2
try except 语句的执行流程如下:
- 首先执行 try 中的代码块,如果执行过程中出现异常,系统会自动生成一个异常对象,该异常对象会提交给 Python 解释器,此过程被称为引发异常。
- 当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块,如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为捕获异常。如果 Python 解释器找不到捕获异常的 except 块,则程序运行终止,Python 解释器也将退出。
[root@kube try]# cat demo.py #coding:utf-8 try: #try 代码块用于引发异常 a = int(input('输入被除数:')) b = int(input('输入除数:')) c = a / b except(ValueError, ArithmeticError): #捕获异常,处理异常 print('您输入的数值有误,只接受正整数') except: #未指定异常类型,表示所有异常 print('未定义异常') print(' 继续执行' ) [root@kube try]# [root@kube try]# py demo.py 输入被除数:3 输入除数:3 继续执行 [root@kube try]# py demo.py 输入被除数:a 您输入的数值有误,只接受正整数 继续执行 [root@kube try]# py demo.py 输入被除数:4 输入除数:0 您输入的数值有误,只接受正整数 继续执行
访问异常信息
如果程序需要在 except 块中访问异常对象的相关信息,可以通过为 except 块添加as a来实现。当 Python 解释器决定调用某个 except 块来处理该异常对象时,会将异常对象赋值给 except 块后的异常变量,程序即可通过该变量来获得异常对象的相关信息。 所有的异常对象都包含了如下几个常用属性和方法: args:该属性返回异常的错误编号和描述字符串。 errno:该属性返回异常的错误编号。 strerror:该属性返回异常的描述宇符串。 with_traceback():通过该方法可处理异常的传播轨迹信息。
Python try except else详解
[root@kube try]# cat demo3.py s = input('请输入除数:') try: result = 20 / int(s) print('20除以%s的结果是: %g' % (s , result)) except ValueError: print('值错误,您必须输入数值') except ArithmeticError: print('算术错误,您不能输入0') else: print('没有出现异常') print("程序继续运行") [root@kube try]# py demo3.py 请输入除数:aa #输入字符串时捕获ValueError 然后告警 值错误,您必须输入数值 程序继续运行 [root@kube try]# py demo3.py 请输入除数:3 #没有捕获异常程序正常执行,执行 else 代码段 20除以3的结果是: 6.66667 没有出现异常 程序继续运行 [root@kube try]#
[root@kube try]# cat demo4.py def else_test(): s = input('请输入除数:') result = 20 / int(s) print('20除以%s的结果是: %g' % (s , result)) def right_main(): try: print('try块的代码,没有异常') except: print('程序出现异常') else: #当希望程序异常不被 except 捕获时将代码放入 else ,程序的错误就不会被 except 捕获 # 将else_test放在else块中 else_test() def wrong_main(): try: print('try块的代码,没有异常') # 将else_test放在try块代码的后面 else_test() except: print('程序出现异常') # else_test() wrong_main() right_main() [root@kube try]# py demo4.py try块的代码,没有异常 请输入除数:0 程序出现异常 try块的代码,没有异常 请输入除数:0 Traceback (most recent call last): File "demo4.py", line 22, inright_main() File "demo4.py", line 12, in right_main else_test() File "demo4.py", line 3, in else_test result = 20 / int(s) ZeroDivisionError: division by zero [root@kube try]#
#对比上面两个输出结果,不难发现,放在 else 块中的代码所引发的异常不会被 except 块捕获
Python try except finally:资源回收
Python 中,finally 语句是与 try 和 except 语句配合使用的,其通常是用来做清理工作的。无论 try 中的语句是否跳入 except 中,最终都要进入 finally 语句,并执行其中的代码块。
Python 完整的异常处理语法结构如下: try: #业务实现代码 except SubException as e: #异常处理块1 ... except SubException2 as e: #异常处理块2 ... else: #正常处理块 finally : #资源回收块 ...
在异常处理语法结构中,只有 try 块是必需的,也就是说: 如果没有 try 块,则不能有后面的 except 块和 finally 块; except 块和 finally 块都是可选的,但 except 块和 finally 块至少出现其中之一,也可以同时出现; 可以有多个 except 块,但捕获父类异常的 except 块应该位于捕获子类异常的 except 块的后面; 不能只有 try 块,既没有 except 块,也没有 finally 块; 多个 except 块必须位于 try 块之后,finally 块必须位于所有的 except 块之后。
finally 语句块和 else 语句块的区别是,else 语句块只有在没有异常发生的情况下才会执行,而 finally 语句则不管异常是否发生都会执行。不仅如此,无论是正常退出、异常退出,还是通过 break、continue、return 语句退出,finally 语句块都会执行。
[root@kube try]# cat demo5.py import os def test(): fis = None try: #执行 try 模块 fis = open("a.txt") except OSError as e: #如果捕获 OSError ,就赋值给 e ,然后打印指定属性的错误信息 print(e.strerror) # return语句强制方法返回 return # ① #如果错误强制退出 #os._exit(1) # ② finally: #最后必须执行 finally # 关闭磁盘文件,回收资源 if fis is not None: #判断 fis 是否为None ,None 就跳过,不是就关闭 try: # 关闭资源 fis.close() except OSError as ioe: print(ioe.strerror) print("执行finally块里的资源回收!") test() [root@kube try]# py demo5.py No such file or directory 执行finally块里的资源回收! [root@kube try]#
在通常情况下,不要在 finally 块中使用如 return 或 raise 等导致方法中止的语句(raise 语句将在后面介绍),一旦在 finally 块中使用了 return 或 raise 语句,将会导致 try 块、except 块中的 return、raise 语句失效。看如下程序:
[root@kube try]# cat demo6.py def test(): try: # 因为finally块中包含了return语句 # 所以下面的return语句失去作用 return True finally: return False a = test() print(a) [root@kube try]# py demo6.py False [root@kube try]#
Python raise用法
很多时候,系统是否要引发异常,可能需要根据应用的业务需求来决定,如果程序中的数据、执行与既定的业务需求不符,这就是一种异常。由于与业务需求不符而产生的异常,必须由程序员来决定引发,系统无法引发这种异常。
如果需要在程序中自行引发异常,则应使用 raise 语句,该语句的基本语法格式为:
raise [exceptionName [(reason)]]
其中,用 [] 括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。
也就是说,raise 语句有如下三种常用的用法:
- raise:单独一个 raise。该语句引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。
- raise 异常类名称:raise 后带一个异常类名称。该语句引发指定异常类的默认实例。
- raise 异常类名称(描述信息):在引发指定异常的同时,附带异常的描述信息。
上面三种用法最终都是要引发一个异常实例(即使指定的是异常类,实际上也是引发该类的默认实例),raise 语句每次只能引发一个异常实例。
[root@kube try]# cat demo7.py def main(): try: # 使用try...except来捕捉异常 # 此时即使程序出现异常,也不会传播给main函数 mtd(3) except Exception as e: print('程序出现异常:', e) # 不使用try...except捕捉异常,异常会传播出来导致程序中止 mtd(3) def mtd(a): if a > 0: raise ValueError("a的值大于0,不符合要求") main() [root@kube try]# py demo7.py 程序出现异常: a的值大于0,不符合要求 #第一次执行 的是 try 里的mtd 由except 捕获异常报出 Traceback (most recent call last): #这个是 try 外面的mtd 报错,执行 raise 报错 File "demo7.py", line 17, inmain() File "demo7.py", line 13, in main mtd(3) File "demo7.py", line 16, in mtd raise ValueError("a的值大于0,不符合要求") ValueError: a的值大于0,不符合要求 [root@kube try]#
except 和 raise 同时使用
在实际应用中对异常可能需要更复杂的处理方式。当一个异常出现时,单靠某个方法无法完全处理该异常,必须由几个方法协作才可完全处理该异常。也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次引发异常,让该方法的调用者也能捕获到异常。
为了实现这种通过多个方法协作处理同一个异常的情形,可以在 except 块中结合 raise 语句来完成。如下程序示范了except 和 raise 同时使用的方法:
[root@kube try]# cat demo10.py class AuctionException(Exception): pass class AuctionTest: def __init__(self, init_price): self.init_price = init_price def bid(self, bid_price): d = 0.0 try: d = float(bid_price) #当 bid 为字符时raise 将异常传递给 except ,标红的将异常传递给 main() except Exception as e: # 此处只是简单地打印异常信息 print("转换出异常:", e) # 再次引发自定义异常 raise AuctionException("竞拍价必须是数值,不能包含其他字符!") # ① raise AuctionException(e) if self.init_price > d: raise AuctionException("竞拍价比起拍价低,不允许竞拍!") initPrice = d def main(): at = AuctionTest(20.4) try: at.bid("df") except AuctionException as ae: # 再次捕获到bid()方法中的异常,并对该异常进行处理 print('main函数捕捉的异常:', ae) #a = AuctionTest(100) #a.bid(90) main() [root@kube try]# py demo10.py 转换出异常: could not convert string to float: 'df' main函数捕捉的异常: 竞拍价必须是数值,不能包含其他字符! [root@kube try]#
这种 except 和 raise 结合使用的情况在实际应用中非常常用。实际应用对异常的处理通常分成两个部分:
- 应用后台需要通过日志来记录异常发生的详细情况;
- 应用还需要根据异常向应用使用者传达某种提示;
在这种情形下,所有异常都需要两个方法共同完成,也就必须将 except 和 raise 结合使用。
如果程序需要将原始异常的详细信息直接传播出去,Python 也允许用自定义异常对原始异常进行包装,只要将上面 ① 号代码改为如下形式即可:
raise AuctionException(e)
上面就是把原始异常 e 包装成了 AuctionException 异常,这种方式也被称为异常包装或异常转译。
自定义异常类
很多时候,程序可选择引发自定义异常,因为异常的类名通常也包含了该异常的有用信息。所以在引发异常时,应该选择合适的异常类,从而可以明确地描述该异常情况。在这种情形下,应用程序常常需要引发自定义异常。
用户自定义异常都应该继承 Exception 基类或 Exception 的子类,在自定义异常类时基本不需要书写更多的代码,只要指定自定义异常类的父类即可。
下面程序创建了一个自定义异常类:
class AuctionException(Exception):
pass
上面程序创建了 AuctionException 异常类,该异常类不需要类体定义,因此使用 pass 语句作为占位符即可。
在大部分情况下,创建自定义异常类都可采用与上面程序相似的代码来完成,只需改变 AuctionException 异常的类名即可,让该异常的类名可以准确地描述该异常。
Python sys.exc_info()方法:获取异常信息
在实际调试程序的过程中,有时只获得异常的类型是远远不够的,还需要借助更详细的异常信息才能解决问题。
捕获异常时,有 2 种方式可获得更多的异常信息,分别是:
- 使用 sys 模块中的 exc_info 方法;
- 使用 traceback 模块中的相关函数。
本节首先介绍如何使用 sys 模块中的 exc_info() 方法获得更多的异常信息。
有关 sys 模块更详细的介绍,可阅读《Python sys模块》。
模块 sys 中,有两个方法可以返回异常的全部信息,分别是 exc_info() 和 last_traceback(),这两个函数有相同的功能和用法,本节仅以 exc_info() 方法为例。
exc_info() 方法会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,分别为 type、value 和 traceback,它们的含义分别是:
- type:异常类型的名称,它是 BaseException 的子类(有关 Python 异常类,可阅读《Python常见异常类型》一节)
- value:捕获到的异常实例。
- traceback:是一个 traceback 对象。
[root@kube try]# cat demo11.py #coding:utf-8 #使用sys 模块之前,需要使用 import 导入 import sys try: x = int(input('请输入一个被除数:')) print("30除以",x,"等于",30/x) except: print(sys.exc_info()) print('其他异常') [root@kube try]# py demo11.py 请输入一个被除数:0 (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'),) 其他异常 [root@kube try]#
要查看 traceback 对象包含的内容,需要先引进 traceback 模块,然后调用 traceback 模块中的 print_tb 方法,并将 sys.exc_info() 输出的 traceback 对象作为参数参入。例如:
[root@kube try]# cat demo12.py #coding:utf-8 #使用 sys 模块之前,需使用 import 引入 import sys #引入traceback模块 import traceback try: x = int(input("请输入一个被除数:")) print("30除以",x,"等于",30/x) except: #print(sys.exc_info()) traceback.print_tb(sys.exc_info()[2]) print("其他异常...") [root@kube try]# py demo12.py 请输入一个被除数:0 File "demo12.py", line 8, inprint("30除以",x,"等于",30/x) 其他异常... [root@kube try]#
Python traceback模块:获取异常信息
除了使用 sys.exc_info() 方法获取更多的异常信息之外,还可以使用 traceback 模块,该模块可以用来查看异常的传播轨迹,追踪异常触发的源头。
[root@kube try]# cat demo13.py class SelfException(Exception): pass def main(): firstMethod() def firstMethod(): secondMethod() def secondMethod(): thirdMethod() def thirdMethod(): raise SelfException("自定义异常信息") main() [root@kube try]# py demo13.py Traceback (most recent call last): File "demo13.py", line 11, inmain() File "demo13.py", line 4, in main firstMethod() File "demo13.py", line 6, in firstMethod secondMethod() File "demo13.py", line 8, in secondMethod thirdMethod() File "demo13.py", line 10, in thirdMethod raise SelfException("自定义异常信息") __main__.SelfException: 自定义异常信息 [root@kube try]#
使用 traceback 模块查看异常传播轨迹,首先需要将 traceback 模块引入,该模块提供了如下两个常用方法:
- traceback.print_exc():将异常传播轨迹信息输出到控制台或指定文件中。
- format_exc():将异常传播轨迹信息转换成字符串。
[root@kube try]# cat demo14.py # 导入trackback模块 import traceback class SelfException(Exception): pass def main(): firstMethod() def firstMethod(): secondMethod() def secondMethod(): thirdMethod() def thirdMethod(): raise SelfException("自定义异常信息") try: main() except: # 捕捉异常,并将异常传播信息输出控制台 traceback.print_exc() # 捕捉异常,并将异常传播信息输出指定文件中 traceback.print_exc(file=open('log.txt', 'a')) [root@kube try]# py demo14.py Traceback (most recent call last): File "demo14.py", line 13, inmain() File "demo14.py", line 5, in main firstMethod() File "demo14.py", line 7, in firstMethod secondMethod() File "demo14.py", line 9, in secondMethod thirdMethod() File "demo14.py", line 11, in thirdMethod raise SelfException("自定义异常信息") SelfException: 自定义异常信息 [root@kube try]#
Python自定义异常类及用法
Python 允许用户自定义异常类型。实际开发中,有时候系统提供的异常类型不能满足开发的需求。这时就可以创建一个新的异常类来拥有自己的异常。
[root@kube try]# cat demo15.py #coding:utf-8 class CustomizeExceptionError(Exception): print('捕获异常!!') pass try: raise CustomizeExceptionError() except CustomizeExceptionError as err: print(err) [root@kube try]# py demo15.py 捕获异常!! [root@kube try]#
[root@kube try]# cat demo16.py class InputError(Exception): '''当输出有误时,抛出此异常''' #自定义异常类型的初始化 def __init__(self, value): self.value = value # 返回异常类对象的说明信息 def __str__(self): return ("{} is invalid input".format(repr(self.value))) try: raise InputError(1) # 抛出 MyInputError 这个异常 except InputError as err: print('error: {}'.format(err)) [root@kube try]# py demo16.py error: 1 is invalid input [root@kube try]#
注意,只要自定义的类继承自 Exception,则该类就是一个异常类,至于此类中包含的内容,并没有做任何规定
Python异常机制使用细则,正确使用Python异常处理机制
成功的异常处理应该实现如下 4 个目标:
- 使程序代码混乱最小化。
- 捕获并保留诊断信息。
- 通知合适的人员。
- 采用合适的方式结束异常活动。
Python logging模块用法快速攻略
启用 logging 模块很简单,直接将下面的代码复制到程序开头: import logging logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')