分享

第八章:异常

 岚风窗 2015-06-30
本章将学习如何创建和引发自定义的异常,以及处理异常的各种方法。

一、什么是异常
Python用异常对象(exception object)来表示异常情况。遇到错误后,会引发异常。如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(Traceback,一种错误信息)终止执行。事实上,每个异常都是一些类的实例,这些实例可以被引发,并且可以用很多种方法进行捕捉,使得程序可以捉住错误并且对其进行处理,而不是让整个程序失败。

二、按自己的方式出错
异常可以在某些东西出错时自动引发。在学习如何处理异常之前,先看一下自己如何引发异常——甚至创建自己的异常类型。

1、raise语句
为了引发异常,可以使用一个类(应该是Exception的子类)或者实例参数调用raise语句。使用类时,程序会自动创建实例。

第一个例子raise Exception引发了一个没有任何有关错误信息的普通异常。后一个例子中,则添加了一些hyperdrive overload错误信息。

内建的异常类有很多。P这些内建异常都可以在exception模块(在内建的命名空间)中找到。可以使用dir函数列出模块的内容


一些最重要的内建异常类
 类名 描述
 Exception 所有异常的基类
 AttributeError 特性引用或赋值失败时引发
 IOError 试图打开不存在文件(包括其他情况)时引发
 IndexError 在使用序列中不存在的索引时引发
 KeyError 在使用映射中不存在的键时引发
 NameError 在找不到名字(变量)时引发
 SyntaxError 在代码为错误形式时引发
 TypeError 在内建操作或者函数应用于错误类型的对象时引发
 ValueError 在内建操作或者函数应用于正确类型的对象,但是该对象使用不合格的值时引发
 ZeroDivisionError 在除法或者模除操作的第二个参数为0时引发

2、自定义异常类
创建自己的异常类就像其他类一样——只是要确保从Exception类继承(不管是间接的或者是直接的,也就是说继承其他的内建异常类也是可以的)

三、捕捉异常

为了捕捉异常并且做出一些错误处理,可以这样重写程序:

如果没有捕捉到异常,它就会被“传播”到调用的函数中。如果在那里依然没有捕捉,这些异常就会“浮”到程序的最顶层。也就是说你可以捕捉到在其他人的函数中所引发的异常。

如果捕捉到了异常,但是又想重新引发它(也就是说要传递异常),那么可以调用不带参数的raise。

注:如果除零行为发生而屏蔽机制被打开,那么calc方法会(隐式地)返回None。换句话说,如果打开了屏蔽机制,那么就不应依赖返回值。

下面的例子,就是分别打开和关闭了屏蔽:

当计算器没有打开屏蔽机制时,ZeroDivisionError被捕捉但已传递了。

四、不止一个except子句




五、用一个块捕捉两个异常
如果需要用一个块捕捉多个类型异常,那么可以将它们作为元组列出

上面的代码中,如果用户输入字符串或者其他类型的值而不是数字,或者第二个数为0,都会打印同样的错误信息。

六、捕捉对象
如果希望在except子句中访问异常对象本身,可以使用两个参数(注:就算要捕捉到多个异常,也只需要向except子句提供一个参数——一个元组)。下面的示例程序会打印异常,但是程序会继续运行


七、真正的全捕捉
如果真的想用一段代码捕捉所有异常,那么可以在except子句中忽略所有的异常类:

现在可以做任何事情了
警告:像这样捕捉所有异常是危险的,因为它会隐藏所有程序员未想到并且未做好准备处理的错误。它同样会捕捉用户终止执行的Ctrl+C企图,以及用sys.exit函数终止程序的企图等等。这时用except Exception,e会更好些,或者对异常对象e进行一些检查。

八、万事大吉
有些情况中,一些坏事发生时执行一段代码是很有用的;可以像对条件循环语句那样,给try/except语句加个else子句:

使用else子句可以实现在8.5节中提到的循环:





百分之百捕捉到所有的异常是不可能的,因为try/except语句中的代码可能会出问题,比如使用旧风格的字符串异常或者自定义的异常类不是Exception类的子类。不过如果需要使用except Exception的话,可以使用上面的技巧在除法程序中打印更加有用的错误信息:


九、最后
最后是Finally子句。它可以用来在可能的异常后进行清理。它和try子句联合使用:

上面的代码中,finally子句肯定会被执行,不管try子句中是否会发生异常。运行这段代码,在程序崩溃之前,对于变量x的清理就完成了,因为使用del语句删除一个变量是非常不负责的清理手段,所以finally子句用于关闭文件或者网络套接字时会非常有用。

还可以在同一条语句中组合使用try、except、finally和else


十、异常和函数
异常和函数能很自然地一起工作。如果异常在函数内引发而不被处理,它就会传播至(浮到)函数调用的地方。

可以看到faulty中产生的异常通过faulty和ignore_exception传播,最终导致了堆栈跟踪。

十一、异常之禅
如果知道某代码可能会导致某种异常,而又不希望程序以堆栈跟踪的形式终止,那么就根据需要添加try/except或者try/finally语句进行处理。
从另一方面来看,某些程序中使用if/else实现会比使用try/except要好。假设有一个字典,希望打印出存储在特定的键下面的值。如果该键不存在,那么什么也不做。代码可以这样写:

如果给程序提供包含名字Jim Green和年龄25(没有职业)的字典的函数,会得到如下结果:

如果添加了职业“camper”,会得到如下输出:

代码非常直观,但是效率不高,解决方案如下:


这里在打印职业时使用加号而不是逗号。否则字符串'Occupation:'在异常引发之前就会被输出。

在查看对象是否存在特定特性时,try/except也很有用。假设想要看某对象是否有write特性,可以使用如下代码:

这里的try子句仅仅访问特性而不用对它做别的有用的事情。如果Attribute异常引发,就证明对象没有这个特性,反之存在该特性。

十二、本章小结
异常对象:异常情况(比如发生错误)可以用异常对象表示。它们可以用几种方法处理,但是如果忽略的话,程序就会终止。
警告:警告类似于异常,但是仅仅打印错误信息
引发异常:可以使用raise语句引发异常。它接受异常类或者异常实例作为参数。还能提供两个参数。如果在expect子句中不使用参数调用raise,它就会“重新引发”该子句捕捉到的异常。
自定义异常类:用继承Exception类的方法可以创建自己的异常类。
捕捉异常:使用try语句的except子句捕捉异常。如果在except子句中不特别指定异常类,那么所有的异常都会被捕捉。异常可以放在元组中以实现多个异常的指定
else子句:除了except子句,可以使用else子句。如果主try块中没有引发异常,else子句就会被执行
finally:如果需要确保某些代码不管是否有异常引发都要执行,那么这些代码可以放置在finally子句中。
异常和函数:在函数内引发异常时,它就会被传播到函数调用的地方。

















































    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多