在代码运行过程中通常会有异常的产生。比如说操作的数据类型不对,对象的方法和变量不存在,文件不存在,网络无法连上,数据库写失败,字符编码解析失败等等。这些异常都是需要被处理的,如果不进行捕捉和处理,那么代码就会停止运行。
为了能够让代码能够在各种情况下运行,需要考虑运行过程中可能产生的各种异常,并且一一使用代码排除它们,使得代码能够尽可能自己排除一些错误运行下去。因此在代码中处理异常是一项非常重要的工作。实际上,在编写代码的时候,几乎75%的时间都是在排除异常。
15.1 异常的捕捉
使用try ... except ... 可以捕捉到异常。如果try代码段出现了错误,那么相关的except语句就会被执行。
try: s=7+'i'except: print('error occur')
上述代码中把int类型和str类型的进行连接,因此产生了错误。此时except语句就执行了。
仅仅打印出错误语句,还是无法知道错误类型是什么。如果想知道错误类型是什么,需要调用sys.exc_info()方法获得。修改代码为
import systry: s=7+'i'except: print(sys.exc_info())
此时运行结果为
(, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), )
这个错误信息是一个tuple,其中第一项是错误类型,第二项是错误值,第三项是当前堆栈信息,是一个traceback对象,里面记录了错误发生的代码行号,线程信息等等。
从第二项可以看到错误的信息是不支持int和str类进行相加操作。
使用sys.exc_info()方法获取的错误信息可读性不太好。可以使用traceback模块的print_exc()或者format_exe()来获取可读性更好的信息。
import tracebacktry: s=7+'i'except: print(traceback.print_exc())
traceback模块综合了sys.exc_info()方法获得的错误信息,更接近人的阅读习惯。
Traceback (most recent call last): File "testexept.py", line 3, in s=7+'i'TypeError: unsupported operand type(s) for +: 'int' and 'str'None
print_exe()方法还提供了写入文件的操作。此时需要提供一个文件的句柄供文件的写入操作。
import tracebacktry: s=7+'i'except: f=open('err.log','a') print(traceback.print_exc(file=f))
15.2 多个except语句
上面的代码中,except语句可以接收所有的参数类型。在实际引用过程中,不同的错误有着不同的处理过程,此时不同的错误需要有不同的代码块。except语句也支持不同的错误参数,一个try语句可以有多个except语句。
try: a=raw_input('divided:') b=raw_input('divide:') c=float(a)/float(b) print(c)except ZeroDivisionError: print('divide zero')except ValueError: print('not a float')except: print(traceback.print_exc())
在上面的示例代码当中,程序从命令行读入了两个字符串a和b,并且把它们转换为浮点数,进行相除。这段代码当中可能出现的错误是读入的字符串并不能转换为浮点数,或者被除数是0。这两个错误在python中默认的错误值为ValueError以及ZeroDivisionError。此时可以分别用except语句来获取不同的错误,并且分别打印不同的语句。如果仍然有除了这两个错误之外的未知错误,可以使用不带参数的except语句进行获取,并且打印出堆栈信息。
运行这段代码,输入非数字的字符
divided:tdivide:ynot a float
再次运行代码,输入被除数为0的情况
divided:9divide:0divide zero
可以看到每一次只有一个except语句被执行了。
如果多个错误的处理过程是一样的,那么也可以用一个tuple把它们括起来处理。
try: a=raw_input('divided:') b=raw_input('divide:') c=float(a)/float(b) print(c)except (ZeroDivisionError, ValueError): print('error')except: print(traceback.print_exc())
这里要注意的是,如果不加括号,那么就是错误的改名操作。
except ZeroDivisionError,ValueError:except ZeroDivisionError as ValueError:
这两个语句是相等的。都不能够捕获ValueError这个错误。而是在这个except中把ZeroDivisionError重命名为ValueError。
15.3 异常的其它特性
try...except...语句截获的异常可以是try中的语句产生了,也可以是try中调用的函数产生的。
def geta(): a=raw_input('divided:') return float(a)def getb(): b=raw_input('divide:') return float(b)try: c= geta()/getb() print(c)except ZeroDivisionError: print('ZeroDivisionError')except ValueError: print('valueError')
在上述代码当中,把获取输入的代码放到了函数里面。此时运行代码看看函数内产生异常是否能被try...except...语句截获
divided:uvalueError
可以看到当输入一个非数字的时候,函数出现的异常被except语句截获了。
即使函数存在于别的模块或者类里面,出现了异常也依旧可以被捕获。当然前提是这个异常在函数和类里面没有被截获。
try中抛出的异常,如果没有在except语句里面截获。那么就会被抛到上一层的try或者顶层程序。
try: try: a=raw_input('divided:') b=raw_input('divide:') c=float(a)/float(b) print(c) except ZeroDivisionError: print('inner except ZeroDivisionError')except ValueError: print('outer except for valueError')
在上述代码当中,里层的try ... except ... 语句并没有去截获ValueError。在发生了ValueError异常的时候,这个异常就被留到了更外一层的try ... except ...语句当中。如果一直到顶层代码依然没有截获这个异常的化,整个程序的执行就会停止,剩余代码不再进行下去。
15.4 else和finally语句
在执行try...except...语句的时候,有时候会出现没有异常产生的情况,此时如果有else语句,则else语句中的代码将会调用。有些情况需要在无论有没有发生错误都要执行的语句,这些语句可以在finally语句块中被调用。
try: a=raw_input('divided:') b=raw_input('divide:') c=float(a)/float(b) print(c)except ZeroDivisionError: print('ZeroDivisionError')except ValueError: print('for valueError')else: print('no error happen')finally: print('clear anyway')
运行一下,
divided:6divide:70.857142857143no error happenclear anyway
可以看到,这个是没有错误产生的情况,else和finally语句都被调用了。
再看看产生了错误的情况
divided:8divide:0ZeroDivisionErrorclear anyway
此时except和finally语句被调用了。finally语句在一些需要释放资源的情况下很有用。比如说读取一个文件,无论在读取文件的时候有没有产生错误,都需要释放文件的句柄,不然就可能会产生内存泄漏。
有人要问为什么专门在finally语句中释放,不能在try语句之后调用了。因为在try语句里面定义的变量,在try之后就访问不到了。而finally依然可以访问到这个变量。
在这里无论哪一种退出try的方式都会调用,无论是return,break还是continue退出try的方式,都会调用finally。
15.5 自定义的异常
用户可以自定义异常的类型,也可以自己产生异常。
用户自定义的异常类型需要继承Exception这个类,而类中的变量以及方法都自己定义。
用户可以使用raise语句来产生异常。
自定义异常类型和产生异常的代码如下:
class MyError(Exception): def __init__(self,msg): self.msg=msgtry: raise MyError('user defined error')except MyError as e: print(e.msg)
可以看到这个异常只有一个数据成员msg。接着在try里面使用raise了这个异常。在except语句中截获了这个异常,并且打印出这个异常的信息。
领取专属 10元无门槛券
私享最新 技术干货