第8章 异常
8.1 什么是异常
每个异常都是一个类的实例,这些实例可以被引发,并且可以用很多种方法进行捕捉,使得程序可以捉住错误并且对其进行处理,而不是让这个程序失败。
8.2 按自己的方式出错
8.2.1 raise语句
使用了内建的Exception异常类:
>>> raise Exception
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
raise Exception
Exception
>>> raise Exception('a test')
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
raise Exception('a test')
Exception: a test
使用dir函数列出模块的内容:
>>> import exceptions
>>> dir(exceptions)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'EnvironmentError', 'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__doc__', '__name__', '__package__']
一些最重要的内建异常类:
8.2.2 自定义异常类
如何创建自己的异常类呢?
就像其他类一样——只是要确保从Exception类继承(不管是间接的或者是直接的,也就是说继承其他的内建异常类也是可以的)。
例如:
class SomeCustomException:
pass
8.3 捕捉异常
关于异常,最有意思的地方是,可以处理它们(通常叫做诱捕或者捕捉异常)。这个功能可以使用try/except来实现。
为了捕捉异常并且做出一些错误处理,可以这样重写程序(使用except):
try:
x = input('enter the first number:')
y = input('enter the second number:')
print x/y
except ZeroDivisionError:
print "the second number can't be zero!"
enter the first number:8
enter the second number:0
the second number can't be zero!
注意 如果没有捕捉异常,它就会被“传播”到调用的函数中。如果在那里依然没有捕获,这些异常就会“浮“到程序的最顶层。
看,没参数
如果捕捉到了异常,但是又想重新引发它(也就是要传递异常),那么可以调用不带参数的raise(还能在捕捉到异常时显式地提供具体异常)。
一个能“屏蔽”ZeroDivisionError(除零错误)的计算器类。如果这个行为被激活,那么计算器就会打印错误信息,而不是让异常传播。如果在与用户进行交互的过程中使用,那么这就有用了,但是如果是在程序内部使用,引发异常会更好。因此“屏蔽”机制就可以关掉了。
class MuffledCaculator:
muffled = False
def calc(self, expr):
try:
return eval(expr)
except ZeroDivisionError:
if self.muffled:
print 'Division by zero is illegal'
else:
raise
注意 如果除零行为发生而屏蔽机制被打开,那么calc方法会(隐式地)返回None。换句话说,如果打开了屏蔽机制,那么就不应该依赖返回值。
这个类的用法示例,分别打开和关闭了屏蔽:
>>> aCa = MuffledCaculator()
>>> aCa.calc('10/2')
5
>>> aCa.calc('10/0')
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
aCa.calc('10/0')
File "/home/henry/dev/Python_Basic_Course/chapter8_Exceptions/classMaffledCalculator.py", line 5, in calc
return eval(expr)
File "<string>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> aCa.muffled = True
>>> aCa.calc('10/0')
Division by zero is illegal
当计算机没有打开屏蔽机制时,ZeroDivisionError被捕捉但已传递了。
8.4 不止一个except子句
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...'
上面的代码,如果用户输入字符串或者其他类型的值,而不是数字,或者第2个数为0,都会打印同样的错误信息。当然,只打印一个错误信息并没有什么帮助。另外一个方案就是继续要求输入数字,直到可以进行除法运算为止。8.8节中会介绍如何实现这一功能。
8.6 捕捉对象
如果希望在except子句中访问异常对象本身,可以使用两个参数(注意,就算要捕捉到多个异常,也只需向except子句提供一个参数——一个元组)。下面的示例程序会打印异常(如果发生的话),但是程序会继续运行:
try:
x = input('enter the first number: ')
y = input('enter the second number: ')
except (ZeroDivisionError, TypeError), e:
print e
注意 在Python3.0中,except子句会被写作except (ZeroDivisionError, TypeError) as e。
8.7 真正的全捕捉
就算程序能处理好几种类型的异常,但是有些异常还会从眼皮底下溜走。比如那个除法程序,在提示符下面直接按回车,不输入任何东西,会得到一个类似下面这样的错误信息(堆栈跟踪):
详细说明见书上。
但是如果真的想用一段代码捕捉所有异常,那么可以在except子句中忽略所有的异常类:
try:
x = input('enter the first number:')
y = input('enter the second number:')
print x/y
except:
print 'something wrong happened...'
警告 想这样捕捉所有异常是危险的,因为它会隐藏程序员未想到并且未做好准备处理的错误。
8.8 万事大吉
关键词:try、except、else
8.5节中提到:继续要求输入数字,直到可以进行除法运算为止。
实现:
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
这里的循环只在没有异常引发的情况下才会退出(由else子句中的break语句退出)。换句话说,只要有错误发生,程序就会不断要求重新输入。
enter the first number: g
invalid input. please try again.
enter the first number: 5
enter the second number: 0
invalid input. please try again.
enter the first number: 6
enter the second number: 3
x/y is 2
之前提到过,可以使用空的except子句来捕捉所有Exceptions类的异常(也会捕捉其所有子类的异常)。
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: 'l'
enter the second number: 5
invalid input: unsupported operand type(s) for /: 'str' and 'int'
please try again.
enter the first number: k
invalid input: name 'k' is not defined
please try again.
enter the first number: 5
enter the second number: 3
x/y is 1
8.9 最后…
最后是Finally子句。它可以用来在可能的异常后进行清理。
x = None
try:
x = 1/0
finally:
print 'cleanning up...'
del x
在try子句之前初始化x的原因是,如果不这样做,由于ZeroDivisionError的存在,x就永远不会被赋值。这样就会导致在finally子句中使用del删除它的时候产生异常,而且这个异常是无法捕捉的。
不管try子句是否发生异常,finally子句肯定会被执行。
因为使用del删除一个变量是非常不负责的清理手段,所以finally子句用于关闭文件或者网络套接字时会非常有用。
还可以在同一条语句中组合使用try、except、finally、else
try:
1/0
except NameError:
print "Unknown variable"
else:
print "that went well!"
finally:
print "cleaning up"
8.10 异常和函数
8.11 异常之禅
参考文献:
1.《Python基础教程(第2版·修订版)》