2.5.5 Python异常处理与程序调试异常
Exception是任何语言必定存在的一部分。Python提供了强大的异常处理机制,可通过捕获异常来提高程序的健壮性。异常处理还具有释放对象、中止循环的运行等作用。在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样就可以知道是否有错,以及出错的原因。在操作系统提供的调用中,返回错误码非常常见。比如打开文件的函数open(),成功时返回文件描述符(就是一个整数),出错时返回-1值。用错误码来表示是否出错十分不便,因为应该返回的正常结果会和错误码混在一起,造成调用者必须用大量的代码来判断是否出错。一旦出错,还要一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息)。所以高级语言通常都内置了一套try...except...finally...的错误处理机制,Python也不例外。
1.try...except语句
try...except语句用于处理问题语句,捕获可能存在的异常。try子句中的代码块用于放置可能出现异常的语句,except子句中的代码块用于处理异常。当异常出现时,Python会自动生成一个异常对象。该对象包括异常的具体信息,以及异常的种类和错误位置。我们举个简单的例子:
#!/usr/bin/env python #-*- coding:utf-8 -*- try: open('test.txt','r') #尝试获取一个不存在的文件 print "该文件是正常的" except IOError: #捕获I/O异常,如果是Python 3.7版本,这里是FileNotFoundError异常 print "该文件不存在" except: print "程序异常!" #其他异常情况
try...except语句后面还可以添加一个finally语句,这种方式主要用于无论异常是否发生,finally子句都会被执行的情况。所有的finally子句都用于关闭因异常而不能释放的系统资源。还是以上面的例子为例,在后面加上finally子句:
#!/usr/bin/env python #-*- coding:utf-8 -*- try: f = open('test1.py','r') try: print f.read() except: print "该文件是正常的" finally: print "释放资源" f.close() except IOError: print "文件不存在!"
我们用finally语句的本意是想关闭因异常而不能释放的系统资源,比如关闭文件。但随着语句的增多,try...finally显然不够简洁,用with...as(上下文管理器)语句可以很简洁地实现以上功能:
with open('test1.py','w') as f: f.write('Hello ') f.write('World')
这样不仅能处理出现异常的情况,而且还可以避免出现在open()一个文件后忘记写close()方法的情况。
此外,当程序中出现错误时,Python会自动引发异常,也可以通过raise语句显式地引发异常。一旦执行了raise语句,raise语句后的代码将不能被执行。示例如下:
#!/usr/bin/env python #-*- encoding:utf-8 -*- try: s = None if s is None: print "s是对空对象" print len(s) except TypeError: print "空对象是没有长度的"
第三行程序判断变量s的值是否为空,如果为空,则抛出异常NameError;由于执行了NameError异常,所以该代码后的代码将不会被执行。
2.调试
当程序中出现异常或错误时,最后的解决方法就是调试程序,那么一般包含哪些方法呢?通常有如下5种方法:
·编辑器自带的调试功能
·print方法
·断言(assert)方法
·logging模块
·pdb方法
Python的专业编程器,例如PyCharm IDE本身就带了Python程序的Debug调试功能,这里就不进行详细说明了,下面只针对其余几种情况来说明。
(1)print方法
print方法很好理解,这也是我们写程序经常用到的一种方法,即我们认为某变量有问题或需要知道某变量时,将它打印出来即可,虽然简单粗暴,但确实有效。但这样也会带来一个问题,复杂的业务逻辑中会有大量变量,这样程序中会充斥着大量的print语句,如果调试完成以后不注意清理,也会带来很多问题。
(2)断言方法
assert语句用于检测某个条件表达式是否为真。
用assert代替print是种很好的选择?想想我们的程序里到处都是print,运行结果也会包含很多垃圾信息。理论上,程序中有print出现的地方,都可以用assert来代替。如果assert语句断言失败,则会引发AssertionError异常。
举个简单的例子:
a = 'hello' assert len(a) == 1
执行下面这段代码,会报错:
AssertionError Traceback (most recent call last) <ipython-input-4-ce3ea8375b99> in <module>() ----> 1 assert len(t) <= 1 AssertionError:
(3)logging模块
当Python程序的代码量达到一定数量时,使用logging就是一种好的选择。logging不仅能输出到控制台,还能写入文件,且能使用TCP协议将日志信息发送到网络,功能十分强大。示例如下:
#!/usr/bin/env python import logging logging.debug('debug message') logging.info('info message') logging.warn('warn message') logging.error('error message') logging.critical('critical message')
执行下面这段代码,结果如下:
WARNING:root:warn message ERROR:root:error message CRITICAL:root:critical message
默认情况下,logging模块会将日志打印到屏幕上,日志级别为WARNING(即只有日志级别高于WARNING的信息才会输出),我们可以在合理的程序中使用logging模块代替print命令,这样写出来的程序在排错时会非常高效。
(4)pdb方法
使用Python的调试器pdb可让程序以单步的方法运行,以便随时查看程序的执行状态。
我们先故意写一个有问题的Python程序,名字叫err.py,内容如下:
#!/usr/bin/env python s = '0' n = int(s) print 10/n
然后在Linux环境下以pdb模式执行程序,如下:
python -m pdb test0.py
l表示可以查看代码全部完整内容,n是一步一步执行代码,p +变量名表示可以随时打印程序中的变量名,这里大家自行演示。
虽然这些方法都有各自的好处,但随着我们开发的项目越来越多,代码量越来越大时,大家会发现,logging模块才是最有效率的办法。