Python基础教程第八章学习笔记——异常

来源:互联网 发布:软件系统租用合同范本 编辑:程序博客网 时间:2024/05/24 15:41

8 异常

异常事件可能是错误(除0),或不希望发生的事情。
为处理这些异常,可在所有可能发生这类事件的地方都使用条件语句。但是,这样会使程序没效率、不灵活、难以阅读。Python的异常对象提供了非常强大的替代解决方案。
本章学习如何创建和引发自定义的异常,及处理异常的各种方法。

8.1 什么是异常

Python用异常对象(exception object)来表示异常情况。遇到错误就会引发异常。
如果异常对象未被处理或捕捉,程序就会用回溯(Traceback)终止执行。
>>> 1/0
Traceback(most recent call last):
....
ZeroDivisionError:...
每一个异常都是一些类的实例,这些实例可以被引发,并可以用很多种方法进行捕捉,使程序可以捉住错误并对其进行处理,而不是让整个程序失败。

8.2 按自己的方式出错

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

8.2.1 raise语句

为引发异常,可使用一个类(应是Exception的子类)或实例参数调用raise语句。
使用类时,程序会自动创建实例。
#使用了内建的Exception异常类的简单例子:
>>> raise Exception                                                #引发一个没有任何有关错误信息的普通异常
Traceback (most recent call last):
    File "<stdin>", line 1, in ?
Exception
>>> raise Exception('hyperdrive overload')              #添加了一些hyperdrive overload错误信息
Traceback (most recent call last):
    File "<stdin>", line 1, in ?
Exception: hyperdrive overload
内建的异常类有很多。这些内建异常都可在exception模块(和内建的命名空间)中找到。可以使用dir函数列出模块的内容:
>>> import exceptions
>>> dir(exceptions)
['ArithmeticError', 'AssertionError', 'AttributeError', ...]
所有这些异常都可用在raise语句中:
>>> raise ArithmeticError
Traceback (most recent call last):
    File "<stdin>", line 1, in ?
ArithmeticError
一些最重要的内建异常类:
Exception                                    #所有异常的基类
AttributeError                              #特性引用或赋值失败时引发
IOError                                        #试图打开不存在文件(包括其他情况)时引发
IndexError                                   #在使用序列中不存在的索引时引发
KeyError                                      #在使用映射中不存在的键时引发
NameError                                   #在找不到名字(变量)时引发
SyntaxError                                   #在代码为错误形式时引发
TypeError                                    #在内建操作或函数应用于错误类型的对象时引发
ValueError                                   #在内建操作或函数应用于正确类型的对象,但是该对象使用不合适的值时引发
ZeroDivisionError                       #在除法或模除操作的第二个参数为0时引发

8.2.2 自定义异常类

内建异常类已包括大部分情况,对很多要求已足够。但有时还需创建自己的异常类。
如在超光速推进装置过载(hyperdrive overload)的例子中,若有个具体的HyperDriveError类来表示超光速推进装置的错误状况会更自然一些。
错误信息够了,但可根据异常所在的类,选择性处理当前类型的异常——想使用特殊的错误处理代码处理超光速推进装置的错误,就需要一个独立于exception模块的异常类。
如何创建自己的异常类?想其他类一样——只要确保从Exception类继承:
class SomeCustomException(Exception): pass

8.3 捕捉异常

异常最有意思的地方就是可以处理它们(即诱捕或捕捉异常)。此功能可通过try/except实现。
#用户输入两个数,然后相除:
x = raw_input('Enter the first number: ')
y = raw_input('Enter the second number: ')
print x/y
程序工作正常,直到用户输入0作为第二个数
Enter the first number: 10
Enter the second number: 0
Traceback (most recent call last):
    File "exceptions.py", line 3, in ?
        print x/y
ZeroDivisionError: integer division or modulo by zero
为了捕捉异常并作出一些错误处理,可这样重写程序:
try:
    x = raw_input('Enter the first number: ')
    y = raw_input('Enter the second number: ')
    print x/y
except ZeroDivisionError:
    print "The second number can't be zero!"
上例中用if语句检查y会更简单一些,但是若需要给程序加入更多除法,就得给每个除法加if语句。而用try/except只需一个错误处理器。
看,没参数
如果捕捉到了异常,但是又想重新引发它(即传递异常),那可以调用不带参数的raise(还能在捕捉到异常时,显示的提供具体异常)
这样做非常有用,例如:
一个能“屏蔽”ZeroDivisionError的计算器类。如果这个行为被激活,计算器就会打印错误信息,而不让异常传播。此功能在与用户进行交互的过程中使用就很有用,如果在程序内部使用,那引发异常会更好些。所以“屏蔽”机制就可以关掉了:
class MuffledCalculator:
    muffled = False
    def calc(self, expr):
        try:
            return eval(expr)
        except ZeroDivisionError:
            if self.muffled:                                           #muffled = True 已屏蔽,与用户交互,异常不传递,打印错误信息(Division by zero is illegal)
                print 'Division by zero is illegal'
            else:
                raise                                                 #无屏蔽,捕捉且传递了异常。在程序内部使用
>>> calculator = MuffledCalculator()
>>> calculator.calc('10/2')
5
>>> calculator.calc('10/0')                               #无屏蔽
Traceback ...
...
...
ZeroDivisionError:...
>>> calculator.muffled = True
>>> calculator.calc('10/0')
Division by zero is illegal

8.4 不止一个except子句

上例中,如果在提示符后输入非数字型的值,就会产生另一个异常:
Enter the first number: 10
Enter the second number: 'Hello world!'
Traceback ...
...
TypeError: ...
因为except子句只寻找ZeroDivisionError异常,所以此异常未被捕捉,导致程序终止。
为了捕捉这个异常,可直接在同一个try/except语句后加上另一个except子句:
try:
    x = int(raw_input('Enter the first number: ')
    y = int(raw_input('Enter the second number: ')
    print x/y
except ZeroDivisionError:
    print "The second number can't be zero!"
except TypeError:
    print "That wasn't a number, was it?"
此种情况下使用if语句实现就比较复杂了,且增加一大堆if语句检查可能的错误情况会使代码相当难读。

8.5 用一个块捕捉两个异常

需用一个块捕捉多个类型异常,可将它们作为元组列出:
try:
    x = input('Enter the first number: ')
    y = input('Enter the second number: ')
    print x/y
except (ZeroDivisionError, TypeError, NameError):
    print 'Your numbers were bogus...'                         #用户输入字符串或其他类型的值,而不是数字,或第二个数字为0,都会打印同样的错误信息。

8.6 捕捉对象

如果希望在except子句中访问异常对象本身,可使用两个参数(即使要捕捉多个异常,也只需向except子句提供一个参数——一个元组)
#想要记录下程序发生的错误(打印异常),但是还想让程序继续运行下去:
try:
    x = input('Enter the first number: ')
    y = input('Enter the second number: ')
    print x/y
except (ZeroDivisionError, TypeError), e:
    print e

8.7 真正的全捕捉

即使程序可处理好几种类型的异常,但是还是会有一些不能捕捉到,例如上例在提示符下直接按回车,会得到类似下面错误信息(堆栈跟踪):
Traceback ...
... 
SyntaxError: ,,,
此异常就逃过了try/except语句的检查——很正常。程序员无法预测会发生什么,也不能对其进行准备,此情况下与其用那些并非捕捉这些异常的try/except语句隐藏异常,还不如让程序立刻崩溃。
但真想用一段代码捕捉所有异常,那可在except子句中忽略所有异常类:
try:
    x = input('Enter the first number: ')
    y = input('Enter the second number: ')
    print x/y
except :
    print 'Something wrong happened...'
现在可做任何事:
Enter the first number: "this" is *completely* illegal 123
Something wrong happened...
这样捕捉所有的异常是危险的,其会隐藏所有程序员未想到且未做好准备处理的错误。还会捕捉用户终止执行的Ctrl+C企图,及用sys.exit函数终止程序的企图等。
此时用except Exception, e会更好些,或对异常对象e进行一些检查。

8.8 万事大吉

可像对循环和条件语句那样,给try/except语句加个else子句:
try:
    print 'A simple task'
except:
    print 'What? Something went wrong?'
else:
    print 'Ah... It went as planned.'
运行后:
A simple task
Ah... It went as planned.
else语句可实现前边除法程序的循环:
while True:
    try:
        x = input('Enter the first number: ')
        y = input('Enter the second number: ')
        value = x/y
        print 'x/y is', value
    except :
        print 'Invalid input, Please try again.'
    else:
        break                           #只有循环没有异常时才会退出。
运行:
Enter the first number: 1
Enter the second number: 0
Invalid input, Please try again.
Enter the first number: 'foo'
Enter the second number: 'bar'
Invalid input, Please try again.
Enter the first number: baz
Invalid input, Please try again.
Enter the first number: 10
Enter the second number: 2
x/y is 5
except子句可捕捉所有Exception类的异常(包括其所有子类的异常)。百分之百捕捉所有异常时是不可能得(try/except语句中的代码可能会出问题——使用旧风格的字符串异常或自定义的异常类不是Exception类的子类)
若需要使用except Exception的话,可捕捉对象从而打印更有用的错误信息:
while True:
    try:
        x = input('Enter the first number: ')
        y = input('Enter the second number: ')
        value = x/y
        print 'x/y is', value
    except Exception,e:
        print 'Invalid input:',e
        print 'Please try again.'
    else:
        break                           #只有循环没有异常时才会退出。
运行:
Enter the first number: 1
Enter the second number: 0
Invalid input: integer division or modulo by zero
Please try again.
Enter the first number: 'x'
Enter the second number: 'y'
Invalid input: unsupported operand type(s) for /: 'str' and 'str'
Please try again.
Enter the first number: quus
Invalid input: name 'quus' is not defined
Please try again.
Enter the first number: 10
Enter the second number: 2
x/y is 5

8.9 最后。。。。。。

finally子句。可用来在可能得异常后进行清理。它和try子句联合使用:
x = None
try:
    x = 1/0
finally:
    print 'Cleaning up...'
   del x
finally子句肯定会被执行(不管try子句是否发生异常)
try子句之前初始化x的原因是:若不初始化,因为ZeroDivisionError的存在,x就永远不会被赋值,这样就会导致finally子句中使用del删除x的时候产生异常,而且此异常无法捕捉。
运行此代码,在程序崩溃之前,对变量x的清理就完成了:
Cleaning up...
Traceback ...
...
ZeroDivisionError: ...
使用del语句删除一个变量是很不负责的清理手段,所有finally子句用于关闭文件或网络套接字时很有用。
可在同一语句中组合使用try、except、finally和else(或其中3个)
try:
    1/0
except NameError:
    print "Unknown variable"
else:
    print "That went well!"
finally:
    print "Cleaning up."

8.10 异常和函数

异常和函数能很自然的一起工作。若异常在函数内引发而未处理,就会传播至(浮到)函数调用的地方。若在那里还未处理异常,会继续传播,一直到达主程序(全局作用域),若在那里无异常处理程序,程序会带着堆栈跟踪终止。例如:
>>> def faulty():
...             raise Exception('Something is wrong')
...
>>> def ignore_exception():
...             faulty()
...
>>> def handle_exception():
...           try:
...                faulty()
...           except:
...                print 'Exception handled'
...
>>> ignore_exception()               #faulty中产生的异常通过faulty和ignore_exception传播,最终导致了堆栈跟踪。
Traceback ...
    File '<stdin>', line1, in?
    File '<stdin>', line2, in ignore_exception
    File '<stdin>', line2, in faulty
Exception: Something is wrong
>>> handle_exception()             #faulty产生的异常同样传到了handle_exception,但在这个函数中被try/except语句处理。
Exception handled

8.11 异常之禅

异常处理不复杂,若知道某段代码可能导致异常,而又不希望程序以堆栈跟踪的形式终止,就可根据需要添加try/except或try/finally语句(或它们组合)进行处理
有时,条件语句也可实现和异常处理同样的功能,但条件语句在自然性和可读性上可能差些。某些时候if/else可能比使用try/exception要好。例如:
#有一字典,希望打印出存储在特定键下的值。若该键不存在,什么也不做
def describePerson(person):
    print 'Description of', person['name']
    print 'Age:', person['age']
    if 'occupation' in person:
        print 'Occupation: ', person['occupation']
若提供包含名字Throatwobbler Mangrove和年龄42(无职业)的字典的函数,会输出:
Description of Throatwobbler Mangrove
Age: 42
若添加职业“camper”,会输出:
Description of Throatwobbler Mangrove
Age: 42
Occupation: camper
以上代码非常直观,但效率不高——程序会查找两次‘Occupation’键:一次看存在,一次获取打印值
另可写为:
def describePerson(person):
    print 'Description of', person['name']
    print 'Age:', person['age']
    try:
         print 'Occupation: ' + person['occupation']                  #用+而不用,是因为如果是逗号,在异常引发之前‘Occupation:’就会输出。
    except KeyError: pass
此程序直接假设‘Occupation’键存在。若存在,直接读取值打印——不用检查其是否真存在。若不存在,引发KeyError异常,被except捕捉。
查看对象是否存在特定特性时,try/except也很有用。
#查看对象是否有write特性
try:
     obj.write
except AttributeError:
    print 'The object is not writeable'
else:
    print 'The object is writeable'
以上是实现第七章介绍的getattr(7。2。8)方法的替代方法。

8.12 小结

异常对象。 异常情况可用异常对象表示。可用几种方法处理,但是忽略的话程序就会终止。
警告。 类似于异常,但是(一般)仅打印错误信息
引发异常。 可用raise语句引发。它接受异常类和异常实例作为参数。还能提供两个参数(异常和错误信息)。如果在except语句中不使用参数调用raise,它就会“重新引发”该子句捕捉到的异常。
自定义异常类。用继承Exception类的方法可创建自己的异常类。
捕捉异常。try语句的except子句捕捉异常。若except子句中不特别指定异常类,那所有异常都会被捕捉。异常可放在元组内来实现多个异常的指定。若给except提供两个参数,第二个参数就会绑定到异常对象上。try/except语句中能包含多个except子句,用来处理不同的异常。
else子句。try除except子句还可使用else子句。若主try块没引发异常,else子句就会被执行
finally。若要确保某些代码不管是否引发异常,都要执行(如清理代码),那这些代码放进finally子句中
异常和函数。 在函数内引发异常,会被传播到函数调用的地方(对方法同样)、

8.12.1 本章新函数

warnings.filterwarnings(action,...)                           用于过滤警告
原创粉丝点击