# 调试技巧 掌握高效的调试技巧,能大大提高开发效率。 ## 打印调试 ### f-string 调试(Python 3.8+) ```python # 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 ``` ### 漂亮打印 ```python 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)) ``` ### 结构化输出 ```python # 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) ``` ## 断点调试 ### 内置 breakpoint() ```python 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 - 打印表达式 # pp - 漂亮打印 # l (list) - 显示当前代码 # w (where) - 显示调用栈 # q (quit) - 退出 ``` ### 条件断点 ```python 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() ``` ### ipdb(增强版 pdb) ```python # pip install ipdb def buggy_function(): import ipdb; ipdb.set_trace() # ipdb 提供语法高亮、Tab 补全等 # 或设置默认调试器 # 在 ~/.pdbrc 或环境变量 PYTHONBREAKPOINT=ipdb.set_trace ``` ## 日志调试 ### 配置日志 ```python 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) ``` ### 日志装饰器 ```python 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 ``` ## 追踪执行 ### trace 模块 ```python 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='.') ``` ### sys.settrace ```python 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) ``` ## 性能分析 ### cProfile ```python 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 ``` ### timeit ```python 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()) # 自动确定合适的运行次数 ``` ## 异常调试 ### 打印完整堆栈 ```python 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__) ``` ### 自定义异常钩子 ```python 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 ``` ### 断言调试 ```python # 启用断言(默认启用) 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 ``` ## 调试工具 ### IDE 调试器 VS Code / PyCharm 提供强大的图形化调试功能: - 断点(条件断点、日志断点) - 变量检查 - 调用栈 - 监视表达式 - 远程调试 ### 远程调试 ```python # pip install debugpy import debugpy # 监听调试连接 debugpy.listen(5678) print("Waiting for debugger...") debugpy.wait_for_client() print("Debugger attached!") # 然后在 VS Code 中连接到 localhost:5678 ``` ## 最佳实践 ::::{grid} 1 :gutter: 2 :::{grid-item-card} 调试流程 1. **重现问题**:创建最小可重现案例 2. **收集信息**:日志、错误消息、堆栈 3. **形成假设**:根据信息推测原因 4. **验证假设**:使用断点、打印、测试 5. **修复并验证**:确保问题真正解决 ::: :::{grid-item-card} 调试技巧 1. **二分法**:在代码中间加断点,缩小问题范围 2. **橡皮鸭调试**:向他人(或橡皮鸭)解释代码 3. **休息一下**:困住时休息往往能找到新思路 4. **查看 git diff**:如果之前工作正常 5. **阅读错误消息**:Python 的错误消息通常很有帮助 ::: ::::