介绍
在计算机编程中,调试(debugging)是查找和修复阻止程序正确运行的缺陷或“bug”的基本过程。虽然 bug 是软件开发中不可避免的一部分,但 Python 提供了一套强大的工具来帮助你高效地诊断和修复它们。
在这个 Lab 中,你将学习几种 Python 中基本的调试技术。我们将涵盖如何:
- 手动引发(raise)和处理(handle)异常,以在发生错误时控制程序流程。
- 将详细的错误回溯(traceback)捕获为字符串,用于日志记录。
- 使用断言(assertions)作为健全性检查(sanity checks),尽早捕获逻辑错误。
- 实现一个灵活的日志记录系统,用于跟踪程序的执行并诊断问题,而不会中断其流程。
引发异常
在这一步,你将学习如何使用 raise 语句手动触发异常。异常是用来指示错误发生的机制。一个 raise 语句由 raise 关键字、对 Exception() 函数的调用,以及传递给 Exception() 的一个有用的错误信息字符串组成。
在函数的前置条件不满足时,在函数内部触发异常,然后在调用该函数的代码中使用 try 和 except 块来处理这些异常,是一种常见的做法。
注意: 如果你不熟悉使用 WebIDE(类似 VS Code),请先在 这个 Lab 学习。
让我们通过一个例子来尝试一下。
- 从左侧的文件浏览器中打开文件
raise_exception.py。 - 将以下代码添加到文件中。这段代码定义了一个函数
box_print,它对无效参数触发异常,然后使用有效和无效的输入调用此函数,并捕获异常。
def box_print(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)
## Handle exceptions when calling the function
for sym, w, h in (('*', 4, 4), ('O', 20, 5), ('x', 1, 3), ('ZZ', 3, 3)):
try:
box_print(sym, w, h)
except Exception as err: ## Catch exception and print error message
print('An exception happened: ' + str(err))
- 现在,从终端运行脚本,查看异常是如何被触发和处理的。
python3 raise_exception.py
你将看到以下输出。前两次对 box_print 的调用成功,而后两次失败,触发了 except 块来打印自定义的错误信息。
****
* *
* *
****
OOOOOOOOOOOOOOOOOOOO
O O
O O
O O
OOOOOOOOOOOOOOOOOOOO
An exception happened: Width must be greater than 2.
An exception happened: Symbol must be a single character string.
将回溯信息获取为字符串
当发生未处理的异常时,Python 会打印一个回溯(traceback),它是导致错误的函数调用链的详细报告。有时,你希望优雅地处理异常,但仍记录完整的堆栈跟踪以供后续调试。traceback 模块允许你执行此操作。
在这一步中,你将学习使用 traceback.format_exc() 将回溯捕获为字符串并将其保存到文件中。
- 从文件浏览器中打开文件
get_traceback.py。 - 添加以下代码。此脚本导入
traceback模块,然后使用try...except块来捕获异常。在except块内部,它将完整的堆栈跟踪写入名为errorInfo.txt的文件中。
import traceback
try:
raise Exception('This is the error message.')
except:
with open('errorInfo.txt', 'w') as error_file:
error_file.write(traceback.format_exc()) ## Write traceback to file
print('The traceback info was written to errorInfo.txt.')
- 在终端中执行脚本。
python3 get_traceback.py
脚本将打印一条确认消息:
The traceback info was written to errorInfo.txt.
- 现在,检查新创建的
errorInfo.txt文件的内容。你可以从文件浏览器中打开它,或者在终端中使用cat命令。
cat errorInfo.txt
该文件将包含完整的堆栈跟踪,类似于以下内容:
Traceback (most recent call last):
File "/home/labex/project/get_traceback.py", line 4, in <module>
raise Exception('This is the error message.')
Exception: This is the error message.
这项技术对于在生产应用中记录错误而又不使程序崩溃非常有用。
使用断言
断言是用于确保你的代码没有明显错误的一种“健全性检查”(sanity checks)。assert 语句会检查一个条件是否为真。如果条件为假,它会引发一个 AssertionError,理想情况下这应该会使你的程序崩溃。这种“快速失败”(fail-fast)的方法可以帮助你更快地确定 Bug 的根本原因。
一个 assert 语句由 assert 关键字、一个要检查的条件以及一个可选的错误消息组成。
在这一步中,你将使用一个断言来验证程序的某个状态。
- 从文件浏览器中打开文件
use_assertions.py。 - 添加以下代码。它检查
pod_bay_door_status的状态。第一个断言会静默通过,但第二个断言会失败并引发一个AssertionError。
## assert statement: check condition, raise AssertionError if False
pod_bay_door_status = 'open'
assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".' ## Passes
pod_bay_door_status = 'I\'m sorry, Dave. I\'m afraid I can\'t do that.'
assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".' ## Raises AssertionError
- 在终端中运行脚本。
python3 use_assertions.py
由于第二个断言失败了,程序将崩溃,你将看到带有自定义消息的 AssertionError。
Traceback (most recent call last):
File "/home/labex/project/use_assertions.py", line 6, in <module>
assert pod_bay_door_status == 'open', 'The pod bay doors need to be "open".'
AssertionError: The pod bay doors need to be "open".
与异常不同,你不应该使用 try...except 来处理断言。AssertionError 信号表明程序中存在需要修复的 Bug。值得注意的是,通过使用 -O(优化)标志运行 Python,可以全局禁用断言,因此它们不应用于必须始终强制执行的输入验证。
实现日志记录
虽然 print() 语句可用于调试,但更强大和灵活的方法是使用 Python 内置的 logging 模块。日志记录允许你在程序运行时跟踪事件,并按严重性对消息进行分类。
在这一步中,你将向程序添加日志记录功能,以帮助诊断 Bug。
- 首先,从文件浏览器中打开文件
factorial_log.py。 - 添加以下代码。此程序尝试计算一个数的阶乘,但其中包含一个 Bug。我们添加了
logging.debug()调用来跟踪每一步中变量的值。logging.basicConfig()函数配置日志系统,以显示级别为DEBUG及以上的消息。
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')
- 在终端中运行脚本。
python3 factorial_log.py
你将看到详细的日志输出与程序的常规输出交错显示。
%(asctime)s - DEBUG - Start of program
%(asctime)s - DEBUG - Start of factorial(5)
%(asctime)s - DEBUG - i is 1, total is 1
%(asctime)s - DEBUG - i is 2, total is 2
%(asctime)s - DEBUG - i is 3, total is 6
%(asctime)s - DEBUG - i is 4, total is 24
%(asctime)s - DEBUG - i is 5, total is 120
%(asctime)s - DEBUG - End of factorial(5)
120
%(asctime)s - DEBUG - End of program
(注意:原始 markdown 中的示例代码存在一个 Bug。此处提供的代码是经过修正的,以提供更好的学习体验。原始 Bug 是 range(n + 1) 从 0 开始,导致 total 始终为 0。修复方法是使用 range(1, n + 1)。)
通过检查日志消息,你可以清楚地看到循环中每次迭代时 i 和 total 的值,这是理解程序流程和查找 Bug 的强大方法。
配置日志记录行为
logging 模块具有高度的可配置性。你可以控制要显示的日志消息的严重性级别,完全禁用日志记录,或者将日志消息重定向到文件而不是屏幕。
日志级别(Logging Levels)
有五个标准的日志级别,每个级别都有一个对应的函数:
| 级别 | 日志函数 | 描述 |
|---|---|---|
DEBUG |
logging.debug() |
用于详细信息,仅在诊断问题时有用。 |
INFO |
logging.info() |
用于记录一般事件或确认事情按预期工作。 |
WARNING |
logging.warning() |
用于指示可能存在但不阻止程序运行的问题。 |
ERROR |
logging.error() |
用于记录导致特定操作失败的错误。 |
CRITICAL |
logging.critical() |
用于即将导致程序终止的致命错误。 |
当你在 basicConfig 中设置一个日志级别时,Python 将显示该级别及所有更高优先级的消息。例如,设置 level=logging.INFO 将显示 INFO、WARNING、ERROR 和 CRITICAL 消息,但不会显示 DEBUG 消息。
禁用日志记录
在调试完成后,你可能希望关闭所有日志消息,而无需从代码中删除 logging 调用。你可以使用 logging.disable() 来实现这一点。它接受一个级别作为参数,并禁用该级别及以下的所有日志消息。例如,logging.disable(logging.CRITICAL) 将抑制所有日志消息。
日志记录到文件
除了将日志打印到控制台,你还可以通过向 logging.basicConfig() 传递 filename 关键字参数,将它们保存到文件中。
让我们来练习一下。
- 打开文件
configure_logging.py。 - 添加以下代码。它配置日志记录,将所有级别为
DEBUG及以上的消息写入名为myProgramLog.txt的文件。
import logging
## Configure logging to write to a file
logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug('This is a debug message.')
logging.info('This is an info message.')
logging.warning('This is a warning message.')
- 在终端中运行脚本。
python3 configure_logging.py
这次,你不会在终端中看到任何输出。
- 检查
myProgramLog.txt文件的内容。
cat myProgramLog.txt
你将看到所有日志消息都已写入文件,并附带了时间戳和严重性级别。
2023-10-27 10:30:00,123 - DEBUG - This is a debug message.
2023-10-27 10:30:00,123 - INFO - This is an info message.
2023-10-27 10:30:00,123 - WARNING - This is a warning message.
这结束了我们对 Python 基本调试技术的介绍。
总结
在这个实验(Lab)中,你学习并实践了几种基本的 Python 调试技术。你首先使用了 raise 语句来创建和触发自定义异常(exception),并使用 try...except 块来处理它们。然后,你学习了如何使用 traceback.format_exc() 将完整的异常回溯(traceback)捕获为字符串,以用于日志记录。
接下来,你探索了 assert 语句,它作为一种执行合理性检查(sanity checks)的方式,以便在程序进入意外状态时“快速失败”(fail fast)。最后,你深入研究了 logging 模块,学习了如何配置它来显示消息、使用不同的日志级别控制输出,以及将日志写入文件以进行持久化记录。
这些技能对于编写健壮、可维护且易于调试的 Python 应用程序至关重要。



