- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议24:遵循异常处理的几点基本原则
现实世界是不完美的,意外和异常会在不经意间发生,从而使我们的生活不得不暂时偏离正常轨道,软件世界也是如此。或因为外部原因,或因为内部原因,程序会在某些条件下产生异常或者错误。为了提高系统的健壮性和用户的友好性,需要一定的机制来处理这种情况。跟其他很多编程语言一样,Python也提供了异常处理机制。Python中常用的异常处理语法是try、except、else、finally,它们可以有多种组合,如try-except(一个或多个),try -except-else;try -finally以及try -except-else-finally等。语法形式如下:
try: <statements> # Run this main action first except <name1>: <statements> # 当 try 中发生name1 的异常时处理 except (name2, name3): <statements> # 当try 中发生name2 或name3 中的某一个异常的时候处理 except <name4> as <data>: <statements> # 当 try 中发生name4 的异常时处理,并获取对应实例 except: <statements> # 其他异常发生时处理 else: <statements> # 没有异常发生时执行 finally: <statements> # 不管有没有异常发生都会执行
最为全面的组合try -except-else-finally异常处理的流程如图3-1所示。
图3-1 异常处理的流程图
异常处理通常需要遵循以下几点基本原则:
1)注意异常的粒度,不推荐在try中放入过多的代码。异常的粒度是人为划分的,在处理异常的时候最好保持异常粒度的一致性和合理性,同时要避免在try中放入过多的代码,即避免异常粒度过大。在try中放入过多的代码带来的问题是如果程序中抛出异常,将会较难定位,给debug和修复带来不便,因此应尽量只在可能抛出异常的语句块前面放入try语句。
2)谨慎使用单独的except语句处理所有异常,最好能定位具体的异常。同样也不推荐使用except Exception或者except StandardError来捕获异常。
在try后面单独使用except语句可以捕获所有的异常,从表面上看这似乎是个不错的做法,但实际上会带来什么问题呢?来看以下简单的例子:
import sys try: print a b =0 print a/b except: sys.exit("ZeroDivisionError:Can not division zero")
程序运行以打印“ZeroDivisionError:Can not division zero”结束,这会让我们以为是发生了除数为零的错误,但实际情况是因为a在使用前并没有定义,程序引发了NameError。而由于单独的except语句的使用,真实的错误往往被掩盖。对上述代码修改如下:
import sys try: print a b =0 print a/b except ZeroDivisionError: sys.exit("ZeroDivisionError:Can not division zero")
运行程序输出NameError异常如下:
Traceback (most recent call last): File "test.py", line 3, in <module> print a NameError: name 'a' is not defined
单独使用except会捕获包括SystemExit,KeyboardInterrupt等在内的各种异常,从而掩盖程序真正发生异常的原因,给debug造成一定的迷惑性。因此需要谨慎使用,最好能在except语句中定位具体的异常。如果在某些情况下不得不使用单独的except语句,最好能够使用raise语句将异常抛出向上层传递。
3)注意异常捕获的顺序,在合适的层次处理异常。Python中内建异常以类的形式出现,Python2.5后异常被迁移到新式类上,启用了一个新的所有异常之母的BaseException类,内建异常有一定的继承结构,如UnicodeDecodeError继承自UnicodeError,而其继承链结构为UnicodeDecodeError -->UnicodeError -->ValueError -->Exception -->BaseException,如图3-2所示。
图3-2 UnicodeDecodeError继承结构示意图
用户也可以继承自内建异常构建自己的异常类,从而在内建类的继承结构上进一步延伸。在这种情况下异常捕获的顺序显得非常重要。为了更精确地定位错误发生的原因,推荐的方法是将继承结构中子类异常在前面的except语句中抛出,而父类异常在后面的except语句抛出。这样做的原因是当try块中有异常发生的时候,解释器根据except声明的顺序进行匹配,在第一个匹配的地方便立即处理该异常。如果将层次较高的异常类在前面进行捕获,往往不能精确地定位异常发生的具体位置。如下例中ValueError声明在前而UnicodeDecodeError在后,当抛出UnicodeDecodeError异常的时候,由于它是ValueError的子类,在ValueError处便直接被捕获了,打印出消息“ValueError occured”,而真正的异常UnicodeDecodeError却被悄然掩盖。
>>> try: ... raise UnicodeDecodeError("pdfdocencoding","a",2,-1,"not support decoding ") ... except ValueError:#ValueError 为UnicodeDecodeError 的父类,捕获异常时却在前面 ... print "ValueError occured" ... except UnicodeDecodeError,e: ... print e ... ValueError occured >>>
因此,异常捕获的顺序非常重要,同时异常应该在适当的位置被处理,一个原则就是如果异常能够在被捕获的位置被处理,那么应该及时处理,不能处理也应该以合适的方式向上层抛出。遇到异常不论好歹就向上层抛出是非常不明智的。向上层传递的时候需要警惕异常被丢失的情况,可以使用不带参数的raise来传递。
try: some_code() except: revert_stuff() raise
4)使用更为友好的异常信息,遵守异常参数的规范。软件最终是为用户服务的,当异常发生的时候,异常信息清晰友好与否直接关系到用户体验。通常来说有两类异常阅读者:使用软件的人和开发软件的人,即用户和开发者。对于用户来说关注更多的是业务。先来看一段异常信息:
Traceback (most recent call last): File "test.py", line 4, in <module> print ItemPriceTable['a'] KeyError: 'a'
如果将这段信息给一个没有软件编程背景的人看,他肯定会觉得如读天书一般,对于用户这并不是一种较为友好的做法,在面对用户的时候异常信息应该以较为清晰和明确的方式显示出来。如下例中当查找一个不在列表的水果价格的时候给出相关的提示信息会比直接抛出KeyError信息要友好得多。
import sys import traceback ItemPriceTable = {"apple":'3.5',"orange":'4',"cheery":"20","mango":"8"} def getprice(itemname): try: price = ItemPriceTable[itemname] return price except KeyError: print "%s can not find in the price table,you should input another kind of fruit." % sys.exc_value # 显示异常相关的提示信息 while 1: itemname = raw_input("Enter the fruit name to get the price or press x to exit: ") if itemname == "x": break price = getprice(itemname) if price != None: print "%s's price is $%s/kg" % (itemname, price)
此外,如果内建异常类不能满足需求,用户可以在继承内建异常的基础上针对特定的业务逻辑定义自己的异常类。但无论是内建异常类,还是用户定义的异常类,在传递异常参数的时候都需要遵守异常参数规范。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论