python王者归来—学习笔记(18)

第十五章 程序错误与异常处理(程序异常及处理语句、多组异常处理程序、抛出异常和Traceback字符串、finally语句、assert断言、日志模块logging)

有时也可以将程序错误(error)称作程序异常(exception),相信每一位写程序的人一定会常常碰上程序错误。Python提供功能可以让我们捕捉异常和撰写异常处理程序,当发生异常被我们捕捉时会去执行异常处理程序,然后程序可以继续执行。

一、简单程序异常及处理。最常见的程序错误可能是除数为0的错误。例如 z = x / y语句,当y为0时执行该程序就会发生ZeroDivisionError:division by zero的错误,所以整个程序就终止了,后续的程序无法继续执行。要捕捉异常与设计异常处理程序,一般适用try - except语句,它的语法格式如下:

try:
    语句
except 异常对象:
    异常处理程序

上述会执行try:下面的语句,如果正常则跳离except部分,如果语句有错误异常,则检查此异常是否是异常对象所指的错误,如果是,代表异常被捕捉了,则执行此异常对象下面的异常处理程序:

def division(x, y):
    try:
        return x/y
    except ZeroDivisionError:  #除数为0异常
        print("除数不可为0")

print(division(10, 2))
print(division(5, 0))
print(division(6, 3))

上述程序当执行到print(division(5, 0))时由于产生了除数为0的ZeroDivisionError异常,这时Python会寻找是否有处理这类异常的exceptZeroDivisionError存在,如果有就表示此异常被捕捉,就去执行相关的错误处理程序,此处打印出“除数不可为0”的错误。函数返回然后打印出结果None,最后继续执行下一行语句print(division(6, 3))。特别需留意的是在try - except的使用中,如果在try:后面的指令产生异常时,这个异常不是我们设计的except 异常对象,表示异常没被捕捉到,这时程序依旧会直接出现错误信息,然后程序终止。

Python在try - except中又增加了else指令,这个指令存放的主要目的是try内的指令正确时,可以执行else内的指令区块,我们可以将这部分指令区块称正确处理程序,这样可以增加程序的可读性。

fn = 'ch15_01.txt'
#这是try - except - else语句
try:
    with open(fn) as file_obj:
        data = file_obj.read()
except FileNotFoundError:
    print("找不到 %s 文件" % fn)
else:
    print(data)


#这是try - except语句
try:   
    with open(fn) as file_obj:
        data = file_obj.read()
        print(data)
except FileNotFoundError:
    print("找不到 %s 文件" % fn)

二、多组异常处理程序。常见的异常对象有以下几种,Python提供了一个通用型的异常对象Exception,它可以捕捉各式的基础异常。

在try: - except的使用中,可以设计多个except捕捉多种异常,也允许设计一个except,捕捉多个异常。两种格式语法如下:

try:
    语句
except 异常对象1:
    异常处理程序1
except 异常对象2:
    异常处理程序2
...



try:
    语句
except (异常对象1, 异常对象2, ...):
    异常处理程序

在之前发生异常同时被捕捉皆是使用我们自建的异常处理程序,Python也支持发生异常时使用系统内置的异常处理信息。此时语法格式如下:

try:
    语句
except 异常对象 as e:
    print(e)

上述e是系统内置的异常处理信息,e可以是任意字符,此处使用e是因为代表error的内涵。当然上述except语法也接受同时处理多个异常对象:

def division(x, y):
    try:
        return x/y
    except (ZeroDivisionError, TypeError) as e:  #2个异常
        print(e)

print(division(10, 2))
print(division(5, 0))
print(division('a', 'b'))
print(division(6, 3))

程序设计许多异常是我们不可预期的,很难一次设想周到,Python提供语法让我们可以一次捕捉所有异常,此时try -except语法如下:

try:
    语句
except:
    异常处理程序

三、抛出异常和Traceback字符串。我们设计程序时如果发生某些状况,我们自己将它定义为异常然后抛出异常信息,程序停止正常往下执行,同时让程序跳到自己设计的except去执行。它的语法如下:

raise Exception('错误信息')  #抛出异常错误信息
...
...
try:
    语句
except Exception as err:    #err是任意变量名称,内容是抛出的异常错误信息
    print("错误信息提示", str(err))  #打印错误信息
def checkPassword(pwd):
    """检查密码长度必须是5到8个字符"""
    pwdlen = len(pwd)
    if pwdlen < 5:
        raise Exception('密码长度不足')
    if pwdlen > 8:
        raise Exception('密码长度太长')
    print('密码长度正确')

for pwd in ('aaabbbccc', 'aaa', 'aabbcc'):
    try:
        checkPassword(pwd)
    except Exception as err:
        print("密码长度检查异常发生:", str(err))

每次程序错误时,如果没有处理异常,则会在屏幕打印出错误信息,即Traceback字符串,在这个字符串中指出程序错误的原因。即使我们设计了异常处理程序,避免错误造成程序中断,屏幕没有打印错误信息,实际上Python还是有记录错误的,我们可以导入traceback模块,就能使用traceback.format_exc( )记录这个Traceback字符串并显示打印出来或者写入日志文件中:

import traceback

def division(x, y):
    try:
        return x/y
    except: #捕捉所有异常
        with open('errlog.txt', 'a') as errlog:
            errlog.write(traceback.format_exc()) #将报错信息写入文件
        print("异常发生")

print(division(10, 2))
print(division(5, 0))
print(division('a', 'b'))
print(division(6, 3))

四、finally。关键词finally功能是和try配合使用,在try之后可以有except或else,这个finally关键词必须放在except和else之后,同时不论是否有异常发生一定会执行这个finally内的程序代码:

def division(x, y):
    try:
        return x/y
    except: #捕捉所有异常
        print("异常发生")
    finally:
        print("阶段任务完成")  #离开函数前先执行此程序代码

print(division(10, 2), "\n")
print(division(5, 0), "\n")
print(division('a', 'b'), "\n")
print(division(6, 3), "\n")

上述程序执行时,如果没有发生异常,程序会先输出字符串“阶段任务完成”,然后返回主程序,输出division( )的返回值。如果程序有异常会先输出字符串“异常发生”,再执行finally的程序代码输出字符串“阶段任务完成”然后返回主程序输出“None”。

五、断言assert。断言主要功能是确保程序执行的某个阶段,必须符合一定的条件,如果不符合这个条件时程序主动抛出异常,让程序终止同时主动打印出异常原因,方便程序开发者排错。它的语法格式如下:assert 条件, '字符串'。上述意义是程序执行至此阶段时测试条件,如果条件响应是True,程序不理会逗号“,”右边的字符串正常往下执行。如果条件响应是False,程序终止同时将逗号“,”右边的字符串输出到Traceback的字符串内:

class Banks():
    #定义银行类
    def __init__(self, uname, money): #初始化客户名称和余额
        self.name = uname
        self.balance = money

    def save_money(self, money):      #存款方法
        #使用断言保证存款金额必须大于0
        assert money > 0, '存款money必须大于0'
        self.balance += money
        print("存款 ", money, " 完成")

    def withdraw_money(self, money):   #取款方法
        #使用断言保证取款金额必须大于0
        assert money > 0, '取款money必须大于0'
        #使用断言保证取款数小于等于存款数
        assert money <= self.balance, '余额不足'
        self.balance -= money
        print("取款 ", money, " 完成")

    def get_balance(self):
        print(self.name, " 目前余额 ", self.balance)

huangbank = Banks('huang', 100)
huangbank.get_balance()
huangbank.save_money(300)
huangbank.get_balance()
huangbank.save_money(-300)
huangbank.get_balance()
huangbank.withdraw_money(700)
huangbank.get_balance()

断言assert一般是用在程序开发阶段,如果整个程序设计好了以后,想要停用断言assert,可以在Windows的命令提示环境执行程序时使用“-O”选项停用断言:

~/python.exe -O D:\python\ch15\ch15_01.py

六、日志模块logging。Python内有提供logging模块,这个模块有提供方法可以让我们使用程序日志logging功能,在使用前须先使用import导入此模块:import logging。logging模块共分5个等级,从最低到最高等级顺序如下:

  • DEBUG等级使用logging.debug( )显示程序日志内容,所显示的内容是程序的小细节,最低层级的内容,感觉程序有问题时可使用它追踪关键变量的变化过程。
  • INFO等级使用logging.info( )显示程序日志内容,所显示的内容是记录程序一般发生的事件。
  • WARNING等级使用logging.warning( )显示程序日志内容,所显示的内容虽然不会影响程序的执行,但是未来可能导致问题的发生。
  • ERROR等级使用logging.error( )显示程序日志内容,通常显示程序在某些状态将引发错误的缘由。
  • CRITICAL等级使用logging.critical( )显示程序日志内容,这是最重要的等级,通常是显示将让整个系统当掉或中断的错误。

可以使用下列函数设定显示信息等级:logging.basicConfig(level=logging.DEBUG)。当设定logging为某一等级时,未来只有此等级或更高等级的logging会被显示:

import logging

logging.basicConfig(level = logging.WARNING) #设定显示warning及以上等级的日志
logging.debug('logging message, DEBUG') 
logging.info('logging message, INFO')
logging.warning('logging message, WARNING')
logging.error('logging message, ERROR')
logging.critical('logging message, CRITICAL')

当我们设定logging的输出等级是WARNING时,较低等级的logging输出就被隐藏了。当了解了上述logging输出等级的特性后,在设计大型程序时,程序设计初期阶段会将logging等级设为DEBUG,如果确定程序大致没问题,就将logging等级设为WARNING,最后再设为CRITICAL。这样就可以不用再像过去一样,在程序设计初期使用print( )记录关键变量的变化,当程序确定完成后,还需要一个一个检查print( )然后将它删除。

上述程序每一个输出前方有WARNING:root:(其他依次类推)前导信息,这是该logging输出模式默认的输出信息,注明输出logging模式。我们可以使用在logging.basicConfig( )方法内增加format格式化输出信息为空字符串‘’的方式,取消显示前导输出信息:logging.basicConfig(level=logging.DEBUG, format='')。

我们可以在format内配合asctime列出系统时间,这样可以列出每一重要阶段关键变量发生的时间。如果想要输出原先logging.xxx( )的输出信息,必须在format内增加message格式。levelname属性是记载目前logging的显示层级是哪一个等级:

import logging

#asctime:系统时间 levelname:日志层级  message:输出信息
logging.basicConfig(level = logging.DEBUG,
                    format = '%(asctime)s - %(levelname)s - %(message)s')
logging.debug('logging message, DEBUG') 
logging.info('logging message, INFO')
logging.warning('logging message, WARNING')
logging.error('logging message, ERROR')
logging.critical('logging message, CRITICAL')

下面程序使用logging追踪factorial阶乘计算的过程:

import logging

logging.basicConfig(level = logging.DEBUG,
                    format = '%(asctime)s - %(levelname)s - %(message)s')

logging.debug('程序开始')

def factorial(n):
    #阶乘函数
    logging.debug('factorial %d 计算开始' % n)
    ans = 1
    for i in range(1, n+1):
        ans *= i
        logging.debug('i = ' + str(i) + ', ans = ' + str(ans))
    logging.debug('factorial %d 计算结束' % n)
    return ans

num = 5
print("factorial(%d) = %d" % (num, factorial(num)))

logging.debug('程序结束')

程序很长时,若将logging输出在屏幕,其实不太方便逐一核对关键变量值的变化,此时我们可以考虑将logging输出到文件,方法是在logging.basicConfig( )中增加filename=“文件名”,这样就可以将logging输出到指定的文件内:

logging.basicConfig(filename='xxx.txt', level=logging.DEBUG,
                    format='%(asctime)s - %(levelname)s - %(message)s')

logging有许多等级,只要设定高等级,Python就会忽略低等级的输出,所以如果我们程序设计完成,也确定没有错误,其实可以将logging等级设为最高等级,所有较低等级的输出将被隐藏:

logging.basicConfig(level=logging.CRITICAL,
                    format='%(asctime)s - %(levelname)s - %(message)s')

也可以使用下列方法停用日志logging: logging.disable(level)。 该语句可以停用该程序代码之后指定等级及以下的所有等级日志信息,如果想停用全部参数可以使用logging.CRITICAL等级,这个方法一般是放在import下方,这样就可以停用所有的logging:

import logging
logging.disable(logging.CRITICAL)  #停用所有logging日志

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值