调试技巧
调试是编程的核心技能。Python 2.7 提供了多种调试工具,从简单的 print 语句到交互式调试器 pdb,再到日志模块 logging。掌握这些工具能大幅缩短定位问题的时间。
print 调试法
最简单直接的调试方式:
def calculate(x, y):
print "DEBUG: x =", x, "y =", y
result = x / y
print "DEBUG: result =", result
return result
calculate(10, 2)
# DEBUG: x = 10 y = 2
# DEBUG: result = 5
格式化输出:
print "DEBUG: value = %r" % value # %r 显示 repr,适合字符串
print "DEBUG: locals() =", locals() # 打印所有局部变量
%r 使用 repr() 格式化,能显示字符串的引号和转义字符,比 %s 更适合调试。
assert 断言
assert 在条件为假时抛出 AssertionError,适合检查"不可能发生"的情况:
def divide(a, b):
assert b != 0, "Divisor cannot be zero"
return a / b
divide(10, 0) # AssertionError: Divisor cannot be zero
注意:assert 可以用 python -O 禁用,不要用它验证用户输入。
pdb 交互式调试器
在代码中插入断点:
import pdb
def process_data(data):
result = []
for item in data:
pdb.set_trace() # 在这里暂停
processed = item * 2
result.append(processed)
return result
process_data([1, 2, 3])
运行后进入 pdb 交互模式:
> script.py(7)process_data()
-> processed = item * 2
(Pdb)
常用 pdb 命令:
| 命令 | 简写 | 作用 |
|---|---|---|
next | n | 执行下一行(不进入函数) |
step | s | 进入函数调用 |
continue | c | 继续执行到下一个断点 |
break | b | 设置断点 |
print | p | 打印变量值 |
list | l | 显示当前代码上下文 |
where | w | 显示调用栈 |
up | u | 移动到上一层调用栈 |
down | d | 移动到下一层调用栈 |
quit | q | 退出调试器 |
(Pdb) p item # 打印 item 的值
(Pdb) p locals() # 打印所有局部变量
(Pdb) l # 显示当前代码
(Pdb) n # 执行下一行
(Pdb) s # 进入函数
(Pdb) c # 继续执行
命令行启动 pdb:
python -m pdb script.py # 从头开始调试
python -m pdb -c continue script.py # 先运行,遇到异常暂停
事后调试:
import pdb
import sys
def exception_handler(exc_type, exc_value, exc_traceback):
print "Exception occurred:"
pdb.post_mortem(exc_traceback)
sys.excepthook = exception_handler
# 现在任何未捕获的异常都会进入 pdb
1 / 0
日志模块 logging
print 适合临时调试,logging 适合长期维护:
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='app.log'
)
logger = logging.getLogger('myapp')
logger.debug('Debug message')
logger.info('Info message')
logger.warning('Warning message')
logger.error('Error message')
logger.critical('Critical message')
日志级别:
| 级别 | 数值 | 用途 |
|---|---|---|
| DEBUG | 10 | 详细调试信息 |
| INFO | 20 | 正常运行信息 |
| WARNING | 30 | 警告,但程序继续 |
| ERROR | 40 | 错误,部分功能失败 |
| CRITICAL | 50 | 严重错误,程序可能终止 |
条件日志:
# 只在调试时输出大量信息
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Complex data: %s", expensive_operation())
多处理器:
import logging
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
# 文件处理器
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.WARNING)
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 格式化
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# DEBUG 输出到控制台,WARNING 以上输出到文件
logger.debug("Debug info") # 只在控制台
logger.error("Error info") # 控制台和文件都有
实际调试场景
追踪变量变化:
class TracedDict(dict):
def __setitem__(self, key, value):
import traceback
print "Setting %s = %r" % (key, value)
traceback.print_stack(limit=3)
super(TracedDict, self).__setitem__(key, value)
d = TracedDict()
d['x'] = 1 # 打印设置位置和调用栈
性能问题定位:
import time
def timed(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print "%s took %.3f seconds" % (func.__name__, elapsed)
return result
return wrapper
@timed
def slow_function():
time.sleep(1)
slow_function() # slow_function took 1.002 seconds
异常追踪:
import traceback
try:
risky_operation()
except Exception:
# 打印完整异常信息到日志
logging.error("Operation failed:\n%s", traceback.format_exc())
调试技巧总结
| 场景 | 推荐工具 |
|---|---|
| 快速查看变量 | print |
| 交互式逐步执行 | pdb |
| 生产环境问题 | logging |
| 检查不可能的情况 | assert |
| 复杂状态追踪 | 自定义类 + traceback |
| 性能问题 | time / timeit / cProfile |
调试的核心是缩小问题范围:先确定问题出现的代码区域,再逐步细化到具体行和变量。