Python代码调试技巧

张开发
2026/6/14 20:00:02 15 分钟阅读

分享文章

Python代码调试技巧
Python代码调试技巧一、调试的基本方法1.1 print调试最简单但有效的调试方法。def calculate_total(items):total 0for item in items:print(f处理项目: {item}) # 调试输出total item[price] * item[quantity]print(f当前总计: {total}) # 调试输出return total# 改进使用更详细的输出def calculate_total(items):total 0for i, item in enumerate(items):print(f[{i}] 项目: {item[name]}, 价格: {item[price]}, 数量: {item[quantity]})subtotal item[price] * item[quantity]total subtotalprint(f 小计: {subtotal}, 累计: {total})return total1.2 使用logging比print更专业的方式。import logginglogging.basicConfig(levellogging.DEBUG)logger logging.getLogger(__name__)def process_data(data):logger.debug(f开始处理数据: {data})result transform(data)logger.debug(f转换结果: {result})return result二、使用pdb调试器2.1 基本使用import pdbdef buggy_function(x, y):result x ypdb.set_trace() # 设置断点return result * 2# 运行时会在断点处暂停2.2 pdb常用命令# l(ist) - 显示当前代码# n(ext) - 执行下一行# s(tep) - 进入函数# c(ontinue) - 继续执行# p variable - 打印变量# pp variable - 美化打印# w(here) - 显示调用栈# u(p) - 上移一层栈# d(own) - 下移一层栈# b(reak) - 设置断点# cl(ear) - 清除断点# q(uit) - 退出调试2.3 条件断点import pdbdef process_items(items):for i, item in enumerate(items):if i 5: # 只在第5个项目时断点pdb.set_trace()process(item)2.4 使用breakpoint()Python 3.7推荐使用breakpoint()代替pdb.set_trace()。def calculate(x, y):result x ybreakpoint() # 更简洁return result * 2三、使用ipdbipdb是pdb的增强版提供更好的用户体验。# 安装pip install ipdb# 使用import ipdbdef debug_function():x 10ipdb.set_trace()y x * 2return y四、远程调试4.1 使用pdb远程调试import pdbimport sysclass RemotePdb(pdb.Pdb):def __init__(self, host127.0.0.1, port4444):import socketself.sock socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.sock.bind((host, port))self.sock.listen(1)print(f等待连接: {host}:{port})conn, addr self.sock.accept()print(f已连接: {addr})pdb.Pdb.__init__(self, stdinconn.makefile(r), stdoutconn.makefile(w))# 使用RemotePdb().set_trace()# 连接: telnet localhost 4444五、使用IDE调试器5.1 VSCode调试配置// .vscode/launch.json{version: 0.2.0,configurations: [{name: Python: 当前文件,type: python,request: launch,program: ${file},console: integratedTerminal},{name: Python: 模块,type: python,request: launch,module: mymodule,args: [--debug]}]}5.2 PyCharm调试- 点击行号设置断点- F8: 单步执行- F7: 进入函数- ShiftF8: 跳出函数- F9: 继续执行- AltF9: 运行到光标处六、断言调试6.1 使用assertdef divide(a, b):assert b ! 0, 除数不能为0assert isinstance(a, (int, float)), a必须是数字assert isinstance(b, (int, float)), b必须是数字return a / b# 禁用断言生产环境# python -O script.py6.2 自定义断言函数def debug_assert(condition, message断言失败):if not condition:import tracebacktraceback.print_stack()raise AssertionError(message)七、日志调试7.1 结构化日志import loggingimport jsonclass JsonFormatter(logging.Formatter):def format(self, record):log_data {timestamp: self.formatTime(record),level: record.levelname,message: record.getMessage(),module: record.module,function: record.funcName,line: record.lineno}return json.dumps(log_data)handler logging.StreamHandler()handler.setFormatter(JsonFormatter())logger logging.getLogger()logger.addHandler(handler)7.2 上下文日志import loggingfrom contextvars import ContextVarrequest_id ContextVar(request_id, defaultNone)class ContextFilter(logging.Filter):def filter(self, record):record.request_id request_id.get()return Truelogger logging.getLogger()logger.addFilter(ContextFilter())八、性能调试8.1 使用cProfileimport cProfileimport pstatsdef slow_function():total 0for i in range(1000000):total ireturn total# 分析性能cProfile.run(slow_function(), profile_stats)# 查看结果stats pstats.Stats(profile_stats)stats.sort_stats(cumulative)stats.print_stats(10)8.2 使用line_profiler# 安装pip install line_profiler# 使用装饰器profiledef slow_function():total 0for i in range(1000000):total ireturn total# 运行# kernprof -l -v script.py8.3 使用timeitimport timeit# 测量代码执行时间time timeit.timeit(sum(range(100)), number10000)print(f执行时间: {time:.4f}秒)# 比较不同实现time1 timeit.timeit([i for i in range(100)], number10000)time2 timeit.timeit(list(range(100)), number10000)九、内存调试9.1 使用tracemallocimport tracemalloc# 开始跟踪tracemalloc.start()# 执行代码data [i for i in range(1000000)]# 获取快照snapshot tracemalloc.take_snapshot()top_stats snapshot.statistics(lineno)# 显示前10个内存占用for stat in top_stats[:10]:print(stat)tracemalloc.stop()9.2 使用memory_profiler# 安装pip install memory_profilerfrom memory_profiler import profileprofiledef memory_intensive():a [1] * (10 ** 6)b [2] * (2 * 10 ** 7)del breturn a# 运行# python -m memory_profiler script.py十、调试装饰器10.1 函数调用跟踪import functoolsdef trace(func):functools.wraps(func)def wrapper(*args, **kwargs):print(f调用 {func.__name__}({args}, {kwargs}))result func(*args, **kwargs)print(f{func.__name__} 返回 {result})return resultreturn wrappertracedef add(a, b):return a b10.2 异常捕获装饰器import functoolsimport tracebackdef catch_exceptions(func):functools.wraps(func)def wrapper(*args, **kwargs):try:return func(*args, **kwargs)except Exception as e:print(f异常在 {func.__name__}: {e})traceback.print_exc()raisereturn wrapper十一、调试技巧11.1 二分查找bug# 注释掉一半代码确定bug在哪一半# 重复这个过程直到找到问题代码11.2 橡皮鸭调试法# 向别人或橡皮鸭解释代码# 在解释过程中往往能发现问题11.3 最小化重现# 创建最小的可重现示例def minimal_example():# 只包含重现bug所需的最少代码pass11.4 检查假设# 不要假设任何事情# 验证每个假设assert isinstance(data, list), fdata应该是list实际是{type(data)}assert len(data) 0, data不应该为空十二、常见bug模式12.1 可变默认参数# 错误def append_to(element, to[]):to.append(element)return to# 正确def append_to(element, toNone):if to is None:to []to.append(element)return to12.2 闭包中的变量# 错误functions []for i in range(5):functions.append(lambda: i)# 所有函数都返回4# 正确functions []for i in range(5):functions.append(lambda xi: x)12.3 浅拷贝vs深拷贝import copyoriginal [[1, 2], [3, 4]]# 浅拷贝shallow original.copy()shallow[0][0] 999print(original) # [[999, 2], [3, 4]] - 被修改了# 深拷贝deep copy.deepcopy(original)deep[0][0] 999print(original) # [[1, 2], [3, 4]] - 没有被修改十三、调试工具集13.1 icecream# 安装pip install icecreamfrom icecream import icdef calculate(x, y):ic(x, y) # 自动显示变量名和值result x yic(result)return result13.2 pysnooper# 安装pip install pysnooperimport pysnooperpysnooper.snoop()def complex_function(x):y x * 2z y 10return z# 自动记录每一行的执行和变量变化13.3 hunter# 安装pip install hunterimport hunter# 跟踪所有函数调用hunter.trace(modulemymodule)十四、调试最佳实践1. 重现bug2. 理解预期行为3. 隔离问题4. 使用版本控制二分查找5. 添加测试用例6. 修复后验证7. 记录解决方案8. 清理调试代码十五、调试检查清单- [ ] 能稳定重现bug吗- [ ] 错误消息说了什么- [ ] 最近改了什么- [ ] 输入数据是什么- [ ] 预期输出是什么- [ ] 实际输出是什么- [ ] 有日志吗- [ ] 有测试吗- [ ] 其他人能重现吗十六、总结调试是软件开发的重要技能。掌握多种调试工具和技巧从简单的print到专业的调试器从性能分析到内存跟踪可以大大提高问题定位和解决的效率。记住好的调试不仅是找到bug更是理解代码的过程。

更多文章