10.1什么是异常
10.1.1错误
从软件方面讲,错误是语法和逻辑上的。语法错误就是语句使用不规范有问题,导致不能被解释器编译。这一类错误必须在程序执行前纠正。
语法正确了,剩下就要处理逻辑错误,逻辑错误有时候不会报错,只是最后显示的结果和想象中不一样,是由于逻辑不完整或者不合法的输入造成的,还可能是逻辑无法生成,计算,或是输出结果需要的过程无法执行。这些错误通常被称为域错误和范围错误。
当解释器检测到一个错误,会报出相应的异常,而程序不在进行下去。
10.1.2异常
异常就是显示程序出现了错误而在正常控制流以外采取的行为,也就是程序不在进行的原因。这个行为又分为两个阶段:首先是引发异常的错误,然后是检测(和采取可能的措施)阶段。最后解释器会报出相应的异常结果和追踪路径。
异常的处理在第二阶段,可以有很多种不同的操作:忽略(记录错误但不采取任何措施,采取补救措施后终止程序),或者减轻错误设法继续执行程序。
更多的错误来源于外部输入,典型的错误就是用户输入的问题,很难预见这类错误。
10.2 Python中的异常
当系统抛出异常,可以看到异常的类型和异常路径,包括错误的名称,原因和发生错误的行号
几个常见的异常
1.NameError:尝试访问一个未声明的变量
>>> abc Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'abc' is not defined
abc没有被定义一个相对应的值,是一个没有初始化的变量,系统没有找到abc对应的值,所以抛出NameError这个异常
2.ZeroDivisionError:除数为0
>>> 123 / 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero
任何数值被0除都会抛出一个ZeroDivisionError异常,意思就是0不能做分母
3.SyntaxError:Python解释器语法错误
>>> s = [123] >>> for i in s File "<stdin>", line 1 for i in s ^ SyntaxError: invalid syntax
for循环语句末尾漏了引号,就是一个很典型的解释器语法错误。SytaxError异常是唯一不是在运行时发生的异常。它指的是代码中有一个不正确的结构,在它被改正前程序无法进行。这些错误一般是在编译时候发生。
4.IndexError:请求的索引超出序列范围
>>> alist = [1, 2, 3, 4] >>> alist[7] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range
IndexError 在尝试使用一个超出范围值索引序列时引发
5.KeyError:请求一个不存在的字典关键字
>>> database = {'name': 'cytues'} >>> database['age'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'age'
使用错误的或者是不存在的键请求字典就会引发一个KeyError异常
6.IOError:输入/输出错误
>>> f = open('sdga') Traceback (most recent call last): File "<stdin>", line 1, in <module> IOError: [Errno 2] No such file or directory: 'sdga'
类似尝试打开一个不存在的磁盘文件一类的操作会引发一个操作系统的输入/输出错误(I/O)。就会引发IOError异常
7.AttributeError:尝试访问未知的对象属性
>>> class like: ... pass ... >>> love = like() >>> like.you Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: class like has no attribute 'you'
使用句点标识法调用一个类的不存在属性,会导致AttributeError异常
10.3检测和处理异常
异常可以通过try语句来检测,任何在try里面的语句都会被检测有无异常
try语句主要有两种形式:try-except和try-finally。这两个语句是互斥的,try只能对应其中的一种。try可以对应多个except,但只能对应一个finally,或者是一个try-except-finally。
10.3.1 try-except语句
try-except语句(以及更复杂的形式)定义了进行异常监控的一段代码,并且提供了处理异常的机制
try: f = open('adas', 'r') except IOError, e: print 'could not open file: ', e
could not open file: [Errno 2] No such file or directory: 'adas' Process finished with exit code 0
try语句让出错代码不至于影响整个程序的运行,而是之间输出错误原因而继续进下一步操作。注意的点,在try语句中出错代码之后的代码不在运行
如果try语句的代码完成后没有异常发生,就会忽略掉except语句继续执行,而如果发生了异常,except语句保存错误的原因,控制流立即跳转到对应的处理器,显示出一个包含错误原因的错误信息,而整个程序不会被中断
核心笔记:忽略代码,继续执行,和向上移交
try语句中异常发生点后的剩余语句永远不会执行。一旦一个异常被引发,就必须决定控制流下一步到达的位置。解释器将搜索处理器,一旦找到,就开始执行处理器中的代码
如果找不到合适的处理器,异常就一直往上移交给调用者去处理,直到找到合适的处理器,如果到达最顶层都没有找到相应的处理器,那么这个异常就是未处理的,显示出跟踪记录和异常后,程序退出运行
10.3.2包装内建函数
书上使用的是float()内建函数做例子,float()函数遇到不合法的参数直接报异常然后终止程序运行。
>>> float(['sdas']) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: float() argument must be a string or a number
>>> float('fpp') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: could not convert string to float: fpp
float函数常见的两种错误:输入类型不正确(只能识别字符串和数字)TypeError和值不正确(不能转换相应的浮点模式)ValueError
想要安全的调用float函数,就要把float函数放到try语句中,使异常发生但是不会终止整个程序
>>> def safe_float(f): ... try: ... return float(f) ... except ValueError: ... pass ... >>> print safe_float('fpp') None
这里调用的值发生了ValueError,所以返回了try语句的None。但是并没有告诉错误的原因,因为except语句用pass给跳了过去
等于直接忽略掉了这个错误
>>> def safe_float(f): ... try: ... s = float(f) ... except ValueError: ... s = 'could not convert non-number to float' ... return s ... >>> print safe_float('fpp') could not convert non-number to float
较好的解决方法。引发了ValueError异常,也显示了错误的原因,便于修改程序
但是这种方法不能处理多种异常
10.3.3带有多个except的try语句
一个try语句可以对应多个except语句,用来挡住多个异常
def safe_float(f): try: s = float(f) except ValueError: s = 'could not convert non-number to float' except TypeError: s = 'object type cannot be converted to float' return s print safe_float('fpp') print safe_float(['adsda'])
could not convert non-number to float object type cannot be converted to float
10.3.4处理多个异常的excep语句
也可以在一个excep语句里处理多个异常
def safe_float(f): try: s = float(f) except (ValueError, TypeError): s = 'argument must be a number or numeric string' return s print safe_float('fpp') print safe_float(['adsda'])
argument must be a number or numeric string argument must be a number or numeric string
10.3.5捕获所有异常
也可以使用Exception捕获所有异常
def safe_float(f): try: s = float(f) except Exception: s = 'argument must be a number or numeric string' return s print safe_float('fpp') print safe_float(['adsda'])
argument must be a number or numeric string argument must be a number or numeric string
还有一种是except语句直接跟:号。这两种可能会忽略很重要的错误,正常情况下这类异常应该让我们所知。
有些异常不是由于错误条件引起的:SystemExit,KeyboardInterupt,SystemExit是由于当前Python应用需要退出时发生的
核心风格:不要处理并忽略所有错误
错误无法避免,try-except的作用是提供一个可以提示错误或处理错误的机制,而不是一个错误过滤器,使用Exception会忽略很多的错误,这样的用法是缺乏工程实践的表现,不赞同这种做法。也不能把大段的代码放到try语句中,只有一小部分可能会引起异常的部分使用try语句,使之不会干扰程序的正常运行
10.3.6“异常参数”
异常也可以有参数,异常引发后它会被传递给异常处理器。参数是作为附加的帮助信息传递给异常处理器的。
try: float(['fpp']) except TypeError, diag: pass print type(diag) print diag
<type 'exceptions.TypeError'> float() argument must be a string or a number
在except中的diag就是异常参数
核心风格:遵循异常参数规范
如果引发一个ValueError,那么最后提供和解释器引发ValueError时一致的参数信息,可以保证代码的一致性,也能避免其他应用程序在使用这个模块的时候发生错误
10.3.7在应用使用我们封装的函数
#coding:utf-8 #定义safe_float函数 def safe_float(obj): try: retval = float(obj) except (ValueError, TypeError), diag: retval = str(diag) return retval #定义主函数 def main(): #打开名为cardlog的文本文件,并传给log log = open('cardlog.txt', 'w') #try-except语句验证carddata文件是否存在 try: #打开carddata文本文件,传给ccfile ccfile = open('carddata.txt', 'r') except IOError, e: log.write('no txns this month\n') log.close() return #将ccfile中的文字读出传给txns txns = ccfile.readlines() ccfile.close() total = 0.00 log.write('account log:\n') #for循环遍历txns for eachTxn in txns: #将txns中每行文字进行float变换 result = safe_float(eachTxn) #使用isinstance函数判断result里的值是否为浮点数 if isinstance(result, float): #计算浮点数的总和 total += result log.write('data... processed\n') else: log.write('ignored: %s' % result) #输出总的浮点值 print '$%.2f (new balance)' % total log.close() if __name__ == '__main__': main()
信用卡交易系统,用来统计余额
10.3.8 else子句
else也能搭配try-excep语句使用,在else语句范围的任何代码运行前,try范围中的所有代码必须完全成功,没有引发异常。
一般else语句用来证明try语句中的内容是正确无误的
10.3.9 finally子句
finally子句是无论异常是否发生,是否捕捉都会发生的一段代码。
try: A except: B else: C finally: D
try-except-else-finally语句的伪代码
10.3.10 try-finally语句
另一种使用finally的方式是finally单独和try连用。try-finally不是用来捕捉异常,作为替代,它常常用来维持一致的行为,而无论异常是否发生。
10.3.11 try-except-else-finally:厨房一锅端
pass
10.4上下文管理
10.4.1 with语句
with语句的目的在于从流程图中把try-except和finally关键字和资源分配释放相关代码统统去掉
10.4.2 pass
10.5 pass
10.6 触发异常
raise语句
raise [SomeException [, args [, traceback]]]
第一个参数,SomeExcpetion,是触发异常的名字。如果有,它必须是一个字符串,类或实例
第二个符号为可选的args(比如参数,值)来传给异常。异常的参数总是作为一个元组传入。
最后一项参数,traceback,同样是可选的。如果有的话,则是当异常触发时新生的一个用于异常-正常化的跟踪记录对象
10.7断言
断言是一句必须等价于布尔真的判定,此外,发生异常也意味着表达式为假。断言通过assert语句实现
如果断言成功则不采取任何措施,否则引发AssertionError的异常
>>> assert 1 == 1 >>> assert 1 == 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
用try-except语句捕获异常
>>> try: ... assert 1 == 0, 'One does not equal zero silly!' ... except AssertionError, args: ... print '%s: %s' % (args.__class__.__name__, args) ... AssertionError: One does not equal zero silly!
10.8标准异常
pass
10.9 创建异常
pass
10.10为什么用异常
如果错误的确发生了,它们一般都归因于用户输入的数据无效。运行环境必须足够强健,来处理应用级别的错误,并提供用户级别的错误信息。就服务器而言,这必须转化为一个“非错误”因为应用必须要成功完成,即使所做的不过是返回一个错误的信息,向用户是提供一个有效的超文本标记语言的网页指明错误
10.11pass
10.12异常和sys模块
另一种获取异常信息的途径是通过sys模块中的exc_info()函数
10.13相关模块
exception:内建异常(永远不导入这个模块)
contextlib:为使用with语句的上下文对象工具
sys:包含各种异常相关的对象和函数