Linux集群之美
上QQ阅读APP看书,第一时间看更新

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模块才是最有效率的办法。