15 |
您所在的位置:网站首页 › python异常处理有何作用 › 15 |
@Author : Roger TX ([email protected])
@Link : https://github.com/paotong999
一、错误与异常
在程序运行过程中,总会遇到各种各样的错误。 有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为bug,bug是必须修复的。 有的错误是用户输入造成的,比如让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。 还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。Python内置了一套异常处理机制,来帮助我们进行错误处理。 二、Python 的异常机制Python 的异常机制主要依赖 try 、except 、else、finally 和 raise 五个关键字: try 关键字后缩进的代码块简称 try 块,它里面放置的是可能引发异常的代码; 在 except 后对应的是异常类型和一个代码块,用于表明该 except 块处理这种类型的代码块; 在多个 except 块之后可以放一个 else 块,表明程序不出现异常时还要执行 else 块; 最后还可以跟一个 finally 块,finally 块用于回收在 try 块里打开的物理资源,异常机制会保证 finally 块总被执行; raise 用于引发一个实际的异常,raise 可以单独作为语句使用,引发一个具体的异常对象;Python 完整的异常处理语法结构如下: try: #业务实现代码 except SubException as e: #异常处理块1 ... except SubException2 as e: #异常处理块2 ... else: #正常处理块 finally : #资源回收块 ... 三、使用try...except捕获异常 try: #业务实现代码 ... except (Error1, Error2, ...) as e: alert 不合法如果在执行 try 块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给 Python 解释器,这个过程被称为引发异常。 当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块 如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为捕获异常。 如果 Python 解释器找不到捕获异常的 except 块,则运行时环境终止,Python 解释器也将退出。 import sys try: a = int(sys.argv[1]) b = int(sys.argv[2]) c = a / b print("您输入的两个数相除的结果是:", c ) except IndexError: print("索引错误:运行程序时输入的参数个数不够") except ValueError: print("数值错误:程序只能接收整数参数") except ArithmeticError: print("算术错误") except Exception: print("未知异常") 四、异常类的继承体系异常类的继承体系 当 Python 解释器接收到异常对象时,如何为该异常对象寻找 except 块呢?上面程序中 except 块的 except IndexError:这意味着每个 except 块都是专门用于处理该异常类及其子类的异常实例。 当 Python 解释器接收到异常对象后,会依次判断该异常对象是否是 except 块后的异常类或其子类的实例,如果是,Python 解释器将调用该 except 块来处理该异常;否则,再次拿该异常对象和下一个 except 块里的异常类进行比较。 Python 异常捕获流程示意图 异常捕获流程在 try 块后可以有多个 except 块,这是为了针对不同的异常类提供不同的异常处理方式。 当程序发生不同的意外情况时,系统会生成不同的异常对象 Python 解释器就会根据该异常对象所属的异常类来决定使用哪个 except 块来处理该异常。 通常情况下,如果 try 块被执行一次,则 try 块后只有一个 except 块会被执行,不可能有多个 except 块被执行。 异常层级关系上面程序通过 sys 模块的 argv 列表来获取运行 Python 程序时提供的参数。 其中 sys.argv[0] 通常代表正在运行的 Python 程序名,sys.argv[1] 代表运行程序所提供的第一个参数,sys.argv[2] 代表运行程序所提供的第二个参数……依此类推。 Python 用 import 例来导入模块,关于模块和导入模块会在后续章节进行详细讲解。上面程序针对 IndexError、ValueError、ArithmeticError 类型的异常,提供了专门的异常处理逻辑。 如果在运行该程序时输入的参数不够,将会发生索引错误,Python 将调用 IndexError 对应的 except 块处理该异常。 如果在运行该程序时输入的参数不是数字,而是字母,将发生数值错误,Python 将调用 ValueError 对应的 except 块处理该异常。 如果在运行该程序时输入的第二个参数是 0,将发生除 0 异常,Python 将调用 ArithmeticError 对应的 except 块处理该异常。 如果在程序运行时出现其他异常,该异常对象总是 Exception 类或其子类的实例,Python 将调用 Exception 对应的 except 块处理该异常。多异常捕获 Python 的一个 except 块可以捕获多种类型的异常。 在使用一个 except 块捕获多种类型的异常时,只要将多个异常类用圆括号括起来,中间用逗号隔开即可,其实就是构建多个异常类的元组。 import sys try: a = int(sys.argv[1]) b = int(sys.argv[2]) c = a / b print("您输入的两个数相除的结果是:", c ) except (IndexError, ValueError, ArithmeticError): print("程序发生了数组越界、数字格式异常、算术异常之一") except: print("未知异常")访问异常信息 如果程序需要在 except 块中访问异常对象的相关信息,则可通过为异常对象声明变量来实现。 当 Python 解释器决定调用某个 except 块来处理该异常对象时,会将异常对象赋值给 except 块后的异常变量,程序即可通过该变量来获得异常对象的相关信息。所有的异常对象都包含了如下几个常用属性和方法: args:该属性返回异常的错误编号和描述字符串。 errno:该属性返回异常的错误编号。 strerror:该属性返回异常的描述宇符串。 with_traceback():通过该方法可处理异常的传播轨迹信息。 def foo(): try: fis = open("a.txt"); except Exception as e: # 访问异常的错误编号和详细信息 print(e.args) # 访问异常的错误编号 print(e.errno) # 访问异常的详细信息 print(e.strerror) foo() 五、else块在 Python 的异常处理流程中还可添加一个 else 块,当 try 块没有出现异常时,程序会执行 else 块。 s = input('请输入除数:') try: result = 20 / int(s) print('20除以%s的结果是: %g' % (s , result)) except ValueError: print('值错误,您必须输入数值') except ArithmeticError: print('算术错误,您不能输入0') else: print('没有出现异常')只有当 try 块没有异常时才会执行 else 块,那么为什么不直接把 else 块的代码放在 try 块的代码后面? 当 try 块没有异常,而 else 块有异常时,放在 else 块中的代码所引发的异常不会被 except 块捕获。 六、finally块在异常处理语法结构中,只有 try 块是必需的,也就是说: 如果没有 try 块,则不能有后面的 except 块和 finally 块; except 块和 finally 块都是可选的,但 except 块和 finally 块至少出现其中之一,也可以同时出现; 不能只有 try 块,既没有 except 块,也没有 finally 块; 可以有多个 except 块,但捕获父类异常的 except 块应该位于捕获子类异常的 except 块的后面; 多个 except 块必须位于 try 块之后,finally 块必须位于所有的 except 块之后。不管 try 块中的代码是否出现异常,也不管哪一个 except 块被执行,甚至在 try 块或 except 块中执行了 return 语句,finally 块总会被执行。 在 try 块、except 块中使用 os.exit(1) 语句来退出 Python 解释器,则 finally 块将失去执行的机会。 调用 sys.exit() 方法退出程序不能阻止 finally 块的执行,这是因为 sys.exit() 方法本身就是通过引发 SystemExit 异常来退出程序的。在通常情况下,不要在 finally 块中使用如 return 或 raise 等导致方法中止的语句,在 finally 块中使用了 return 或 raise 语句,将可能会导致 try 块、except 块中的 return、raise 语句失效。 如果 Python 程序在执行 try 块、except 块时遇到了 return 或 raise 语句 这两条语句都会导致该方法立即结束,那么系统执行这两条语句并不会结束该方法,而是去寻找该异常处理流程中的 finally 块 如果没有找到 finally 块,程序立即执行 return 或 raise 语句,方法中止 如果找到 finally 块,系统立即开始执行 finally 块,当 finally 块执行完成后,系统才会再次跳回来执行 try 块、except 块里的 return 或 raise 语句 如果在 finally 块里也使用了 return 或 raise 等导致方法中止的语句,finally 块己经中止了方法,系统将不会跳回去执行 try 块、except 块里的任何代码。 七、Python raise用法Python 允许程序自行引发异常,自行引发异常使用 raise 语句来完成。 如果需要在程序中自行引发异常,则应使用 raise 语句。raise 语句有如下三种常用的用法: raise 单独一个 raise。该语句引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。 raise 异常类:raise 后带一个异常类。该语句引发指定异常类的默认实例。 raise 异常对象:引发指定的异常对象。上面三种用法最终都是要引发一个异常实例(即使指定的是异常类,实际上也是引发该类的默认实例),raise 语句每次只能引发一个异常实例。 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()自定义异常类 用户自定义异常都应该继承 Exception 基类或 Exception 的子类,在自定义异常类时基本不需要书写更多的代码,只要指定自定义异常类的父类即可。下面程序创建了一个自定义异常类(程序一): class AuctionException(Exception): pass 程序创建了 AuctionException 异常类,该异常类不需要类体定义,因此使用 pass 语句作为占位符即可。 在大部分情况下,创建自定义异常类都可采用与程序一相似的代码来完成,只需改变 AuctionException 异常的类名即可,让该异常的类名可以准确地描述该异常。 except 和 raise 同时使用 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) except Exception as e: # 此处只是简单地打印异常信息 print("转换出异常:", e) # 再次引发自定义异常 raise AuctionException("竞拍价必须是数值,不能包含其他字符!") # ① raise AuctionException(e) # 原始异常 e 包装成了 AuctionException 异常 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) main()except 和 raise 结合使用的情况在实际应用中非常常用。实际应用对异常的处理通常分成两个部分: 应用后台需要通过日志来记录异常发生的详细情况; 应用还需要根据异常向应用使用者传达某种提示;Python 也允许用自定义异常对原始异常进行包装,只要将上面 ① 号代码改为如下形式即可: raise AuctionException(e) 上面就是把原始异常 e 包装成了 AuctionException 异常,这种方式也被称为异常包装或异常转译。 raise 不需要参数 在使用 raise 语句时可以不带参数,此时 raise 语句将会自动引发当前上下文激活的异常;否则,通常默认引发 RuntimeError 异常。 八、Python 异常使用规范不要过度使用异常 把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以引发异常来代替错误处理。 使用异常处理来代替流程控制。不要使用过于庞大的 try 块 在 try 块里放置大量的代码,然后紧跟大量的 except 块,增加了编程复杂度。不要忽略捕获到的异常 处理异常。对异常进行合适的修复,然后绕过异常发生的地方继续运行;或者用别的数据进行计算,以代替期望的方法返回值;或者提示用户重新操作……总之,程序应该尽量修复异常,使程序能恢复运行。 重新引发新异常。把在当前运行环境下能做的事情尽量做完,然后进行异常转译,把异常包装成当前层的异常,重新传给上层调用者。 在合适的层处理异常。如果当前层不清楚如何处理异常,就不要在当前层使用 except 语句来捕获该异常,让上层调用者来负责处理该异常。 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |