Python编程快速上手—2.自动化任务之调试

3.调试

调试器是 IDLE 的一项功能,它可以一次执行一条指令,在代码运行时,让你有机会检查变量的值,并追踪程序运行时值的变化。这比程序全速运行要慢得多,但可以帮助你查看程序运行时其中实际的值,而不是通过源代码推测值可能是什么。

3.1 抛出异常

抛出异常相当于是说:“停止运行这个函数中的代码,将程序执行转到 except 语句 ”。

如果没有 try 和 except 语句覆盖抛出异常的 raise 语句,该程序就会崩溃,并显
示异常的出错信息。

  • 抛出异常使用 raise 语句
  • 调用Exception()函数 (将包含有用的出错信息的字符串传递给Exception()函数)

通常是调用该函数的代码知道如何处理异常,而不是该函数本身。所以你常常
会看到 raise 语句在一个函数中,try 和 except 语句在调用该函数的代码中。

def boxPrint(symbol, width, height):
    if len(symbol) != 1:
        raise Exception('Symbol must be a single character string.')
    if width <= 2:
        raise Exception('Width must be greater than 2.')
    if height <= 2:
        raise Exception('Height must be greater than 2.')
    print(symbol * width)
    for i in range(height - 2):
        print(symbol + (' ' * (width - 2)) + symbol)
    print(symbol * width)


for sym, w, h in (('*', 4, 4), ('O', 20, 5), ('x', 1, 3), ('ZZ', 3, 3)):
    boxPrint(sym, w, h)  # 程序崩溃
    try:
        boxPrint(sym, w, h)
    except Exception as err:
        print('An exception happened: ' + str(err))

如果没有 try 和 except 语句覆盖抛出异常的 raise 语句,该程序就会崩溃,并显
示异常的出错信息。

使用 try 和 except 语句,你可以更优雅地处理错误,而不是让整个程序崩溃。

3.2 取得反向跟踪的字符串

反向跟踪包含了出错消息、导致该错误的代码行号,以及导致该错误的函数调用的序列。这个序列称为“调用栈”。

在从多个位置调用函数的程序中,调用栈就能帮助你确定哪次调用导致了错误。

只要抛出的异常没有被处理,Python 就会显示反向跟踪。

使用Python的traceback模块将反向跟踪信息写入一个日志文件,并让程序继续运行。稍后,在准备调试程序时,可以检查该日志文件。

import traceback

def spam():
    bacon()

def bacon():
    raise Exception('This is the error message.')

try:
    spam()
except:
    errorFile = open('errorInfo.txt', 'a')
    errorFile.write(traceback.format_exc() + '\n' + '\n')
    errorFile.close()
    print('The traceback info was written to errorInfo.txt.')

write() 方法的返回值是 116,因为 116 个字符被写入到文件中。反向跟踪文本
被写入 errorInfo.txt。

3.3 断言

断言针对的是程序员的错误,而不是用户的错误。对于那些可以恢复的错误(诸如
文件没有找到,或用户输入了无效的数据),请抛出异常,而不是用 assert 语句检测它。

>>> podBayDoorStatus = 'open'
>>> assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
>>> podBayDoorStatus = 'I\'m sorry, Dave. I\'m afraid I can't do that.''
>>> assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
Traceback (most recent call last):
 File "<pyshell#10>", line 1, in <module>
 assert podBayDoorStatus == 'open', 'The pod bay doors need to be "open".'
AssertionError: The pod bay doors need to be "open".

在使用这个变量的程序中,基于这个值是’open’的假定,我们可能写下了大量的代码,即这些代码依赖于它是 ‘open’,才能按照期望工作。所以添加了一个断言,确保假定 podBayDoorStatus 是 ‘open’ 是对的。

def switchLights(stoplight):
    for key in stoplight.keys():
        if stoplight[key] == 'green':
            stoplight[key] = 'yellow'
        elif stoplight[key] == 'yellow':
            stoplight[key] = 'red'
        elif stoplight[key] == 'red':
            stoplight[key] = 'green'
    assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)


market_2nd = {'ns': 'green', 'ew': 'red'}
switchLights(market_2nd)

运行结果:

Traceback (most recent call last):
  File "F:/Open/Project/Python/untitled/pythontest.py", line 13, in <module>
    switchLights(market_2nd)
  File "F:/Open/Project/Python/untitled/pythontest.py", line 9, in switchLights
    assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)
AssertionError: Neither light is red! {'ns': 'yellow', 'ew': 'green'}

这里重要的一行是 AssertionError。虽然程序崩溃并非如你所愿,但它马上指
出了心智正常检查失败:两个方向都没有红灯,这意味着两个方向的车都可以走。
在程序执行中尽早快速失败,可以省去将来大量的调试工作。

在运行 Python 时传入-O 选项,可以禁用断言。

断言是针对开发的,不是针对最终产品。当你将程序交给其他人运行时,它应该没有缺陷,不需要进行心智正常检查。

3.4 日志

记日志是一种很好的方式,可以理解程序中发生的事,以及事情发生的顺序。Python 的 logging 模块使得你很容易创建自定义的消息记录。这些日志消息将描述程序执行何时到达日志函数调用,并列出你指定的任何变量当时的值。另一方面,缺失日志信息表明有一部分代码被跳过,从未执行。

3.4.1 使用日志模块

将下面的代码复制到程序顶部(但在 Python 的#!行之下):

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

当 Python 记录一个事件的日志时,它会创建一个LogRecord对象,保存关于该事件的信息。logging 模块的函数让你指定想看到的这个LogRecord对象的细节,以及希望的细节展示方式。

import logging
logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s- %(message)s')
logging.debug('Start of program')


def factorial(n):
    logging.debug('Start of factorial(%s%%)' % (n))
    total = 1
    for i in range(1, n + 1):
        total *= i
        logging.debug('i is ' + str(i) + ', total is ' + str(total))
        logging.debug('End of factorial(%s%%)' % (n))
    return total


print(factorial(5))
logging.debug('End of program')

debug() 函数将调用 basicConfig(),打印一行信息。这行信息的格式是我们在 basicConfig()函数中指定的,并且包括我们传递给 debug() 的消息。

运行结果:

120
 2018-07-23 21:18:29,402 - DEBUG- Start of program
 2018-07-23 21:18:29,402 - DEBUG- Start of factorial(5%)
 2018-07-23 21:18:29,402 - DEBUG- i is 1, total is 1
 2018-07-23 21:18:29,402 - DEBUG- End of factorial(5%)
 2018-07-23 21:18:29,402 - DEBUG- i is 2, total is 2
 2018-07-23 21:18:29,402 - DEBUG- End of factorial(5%)
 2018-07-23 21:18:29,402 - DEBUG- i is 3, total is 6
 2018-07-23 21:18:29,402 - DEBUG- End of factorial(5%)
 2018-07-23 21:18:29,402 - DEBUG- i is 4, total is 24
 2018-07-23 21:18:29,402 - DEBUG- End of factorial(5%)
 2018-07-23 21:18:29,402 - DEBUG- i is 5, total is 120
 2018-07-23 21:18:29,402 - DEBUG- End of factorial(5%)
 2018-07-23 21:18:29,402 - DEBUG- End of program

logging.debug() 调用不仅打印出了传递给它的字符串,而且包含一个时间戳和单词 DEBUG。

日志消息是给程序员的,不是给用户的。

对于用户希望看到的消息,例如“文件未找到”或者“无效的输入,请输入一个数字”,应该使用 print() 调用。

我们不希望禁用日志消息之后,让用户看不到有用的信息。

3.4.2 日志级别

“日志级别”提供了一种方式,按重要性对日志消息进行分类。

从最不重要到最重要。利用不同的日志函数,消息可以按某个级别记入日志。

级别日志函数描述
DEBUGlogging.debug()最低级别,用于小细节
INFOlogging.info()用于记录程序中一般事件的信息
WARNINGlogging.warning()用于表示可能的问题
ERRORlogging.error()用于记录错误,它导致程序做某事失败
CRITICALlogging.critical()用于表示致命的错误,导致程序完全停止工作

你可能只对错误感兴趣。在这种情况下,可以将 basicConfig() 的 level 参数设置为 logging.ERROR,这将只显示 ERROR和 CRITICAL 消息,跳过 DEBUG、INFO 和 WARNING 消息。

logging.disable() 函数禁用了这些消息,这样就不必进入到程序中,手工删除所有的日志调用。只要向logging.disable()传入一个日志级别,它就会禁止该级别和更低级别的所有日志消息。

3.4.3 将日志记录到文件

logging.basicConfig() 函数接受 filename 关键字参数

import logging
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值