4.4. 调试技巧
掌握高效的调试技巧,能大大提高开发效率。
4.4.1. 打印调试
4.4.1.1. f-string 调试(Python 3.8+)
# Python 3.8+ 的 = 说明符
name = "Alice"
age = 30
score = 95.5
# ❌ 传统方式
print(f"name={name}, age={age}, score={score}")
# ✅ 使用 = 说明符
print(f"{name=}, {age=}, {score=}")
# 输出: name='Alice', age=30, score=95.5
# 支持表达式
items = [1, 2, 3]
print(f"{len(items)=}")
# 输出: len(items)=3
print(f"{items[0] + items[1]=}")
# 输出: items[0] + items[1]=3
4.4.1.2. 漂亮打印
from pprint import pprint, pformat
data = {
'users': [
{'name': 'Alice', 'scores': [90, 85, 92]},
{'name': 'Bob', 'scores': [88, 91, 87]},
],
'metadata': {'version': '1.0', 'count': 2}
}
# 格式化输出
pprint(data, indent=2, width=60)
# 获取格式化字符串
formatted = pformat(data)
print(formatted)
# 用于日志
import logging
logging.debug("Data: %s", pformat(data))
4.4.1.3. 结构化输出
# pip install rich
from rich import print
from rich.console import Console
from rich.table import Table
console = Console()
# 彩色打印
print("[bold red]Error![/bold red] Something went wrong")
# 表格
table = Table(title="Users")
table.add_column("Name", style="cyan")
table.add_column("Score", style="green")
table.add_row("Alice", "90")
table.add_row("Bob", "85")
console.print(table)
# 检查对象
from rich import inspect
inspect(list, methods=True)
4.4.2. 断点调试
4.4.2.1. 内置 breakpoint()
def process_data(items):
result = []
for item in items:
breakpoint() # Python 3.7+,等价于 import pdb; pdb.set_trace()
processed = transform(item)
result.append(processed)
return result
# 常用 pdb 命令
# n (next) - 下一行
# s (step) - 进入函数
# c (continue) - 继续执行
# p <expr> - 打印表达式
# pp <expr> - 漂亮打印
# l (list) - 显示当前代码
# w (where) - 显示调用栈
# q (quit) - 退出
4.4.2.2. 条件断点
def process_items(items):
for i, item in enumerate(items):
if i == 50: # 只在特定条件下暂停
breakpoint()
result = process(item)
# 或使用环境变量
import os
if os.environ.get('DEBUG'):
breakpoint()
4.4.2.3. ipdb(增强版 pdb)
# pip install ipdb
def buggy_function():
import ipdb; ipdb.set_trace()
# ipdb 提供语法高亮、Tab 补全等
# 或设置默认调试器
# 在 ~/.pdbrc 或环境变量 PYTHONBREAKPOINT=ipdb.set_trace
4.4.3. 日志调试
4.4.3.1. 配置日志
import logging
# 基本配置
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 使用
logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message")
logger.exception("Exception occurred") # 自动附带堆栈
# 临时调整级别
logger.setLevel(logging.DEBUG)
4.4.3.2. 日志装饰器
import logging
import functools
import time
def log_call(logger=None):
"""记录函数调用的装饰器"""
def decorator(func):
nonlocal logger
if logger is None:
logger = logging.getLogger(func.__module__)
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger.debug(f"Calling {func.__name__}({args}, {kwargs})")
start = time.time()
try:
result = func(*args, **kwargs)
logger.debug(f"{func.__name__} returned {result} in {time.time()-start:.4f}s")
return result
except Exception as e:
logger.exception(f"{func.__name__} raised {e}")
raise
return wrapper
return decorator
@log_call()
def process(data):
return data * 2
4.4.4. 追踪执行
4.4.4.1. trace 模块
import trace
import sys
# 追踪执行
tracer = trace.Trace(
ignoredirs=[sys.prefix, sys.exec_prefix],
trace=1, # 打印每行
count=1 # 统计执行次数
)
tracer.run('main()')
# 获取覆盖率报告
results = tracer.results()
results.write_results(show_missing=True, coverdir='.')
4.4.4.2. sys.settrace
import sys
def trace_calls(frame, event, arg):
"""简单的调用追踪"""
if event == 'call':
filename = frame.f_code.co_filename
funcname = frame.f_code.co_name
lineno = frame.f_lineno
print(f"Call: {funcname} in {filename}:{lineno}")
return trace_calls
# 启用追踪
sys.settrace(trace_calls)
# 你的代码
process_data()
# 禁用追踪
sys.settrace(None)
4.4.5. 性能分析
4.4.5.1. cProfile
import cProfile
import pstats
from io import StringIO
def profile_function(func):
"""性能分析装饰器"""
def wrapper(*args, **kwargs):
profiler = cProfile.Profile()
try:
return profiler.runcall(func, *args, **kwargs)
finally:
stream = StringIO()
stats = pstats.Stats(profiler, stream=stream)
stats.sort_stats('cumulative')
stats.print_stats(20)
print(stream.getvalue())
return wrapper
@profile_function
def slow_function():
# 你的代码
pass
# 命令行使用
# python -m cProfile -s cumulative script.py
4.4.5.2. timeit
import timeit
# 简单计时
print(timeit.timeit('"-".join(str(n) for n in range(100))', number=10000))
# 比较两种实现
def method1():
return [i ** 2 for i in range(1000)]
def method2():
return list(map(lambda x: x ** 2, range(1000)))
print(timeit.timeit(method1, number=1000))
print(timeit.timeit(method2, number=1000))
# 使用 Timer 类
timer = timeit.Timer(method1)
print(timer.autorange()) # 自动确定合适的运行次数
4.4.6. 异常调试
4.4.6.1. 打印完整堆栈
import traceback
try:
risky_operation()
except Exception as e:
# 打印完整堆栈
traceback.print_exc()
# 获取堆栈字符串
tb_str = traceback.format_exc()
logger.error(tb_str)
# 获取堆栈列表
tb_list = traceback.format_exception(type(e), e, e.__traceback__)
4.4.6.2. 自定义异常钩子
import sys
import traceback
import logging
def exception_hook(exc_type, exc_value, exc_traceback):
"""全局异常钩子"""
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logging.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = exception_hook
4.4.6.3. 断言调试
# 启用断言(默认启用)
def process(data):
assert data is not None, "Data cannot be None"
assert len(data) > 0, f"Data is empty, got {data}"
result = calculate(data)
assert isinstance(result, dict), f"Expected dict, got {type(result)}"
return result
# 禁用断言(生产环境)
# python -O script.py
4.4.7. 调试工具
4.4.7.1. IDE 调试器
VS Code / PyCharm 提供强大的图形化调试功能:
断点(条件断点、日志断点)
变量检查
调用栈
监视表达式
远程调试
4.4.7.2. 远程调试
# pip install debugpy
import debugpy
# 监听调试连接
debugpy.listen(5678)
print("Waiting for debugger...")
debugpy.wait_for_client()
print("Debugger attached!")
# 然后在 VS Code 中连接到 localhost:5678
4.4.8. 最佳实践
调试流程
重现问题:创建最小可重现案例
收集信息:日志、错误消息、堆栈
形成假设:根据信息推测原因
验证假设:使用断点、打印、测试
修复并验证:确保问题真正解决
调试技巧
二分法:在代码中间加断点,缩小问题范围
橡皮鸭调试:向他人(或橡皮鸭)解释代码
休息一下:困住时休息往往能找到新思路
查看 git diff:如果之前工作正常
阅读错误消息:Python 的错误消息通常很有帮助