Python代码调试技巧
一、调试的基本方法
1.1 print调试
最简单但有效的调试方法。
def calculate_total(items):
total = 0
for item in items:
print(f"处理项目: {item}") # 调试输出
total += item['price'] * item['quantity']
print(f"当前总计: {total}") # 调试输出
return total
# 改进:使用更详细的输出
def calculate_total(items):
total = 0
for i, item in enumerate(items):
print(f"[{i}] 项目: {item['name']}, 价格: {item['price']}, 数量: {item['quantity']}")
subtotal = item['price'] * item['quantity']
total += subtotal
print(f" 小计: {subtotal}, 累计: {total}")
return total
1.2 使用logging
比print更专业的方式。
import logging
logging.basicConfig(level=logging.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 pdb
def buggy_function(x, y):
result = x + y
pdb.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 pdb
def 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 + y
breakpoint() # 更简洁
return result * 2
三、使用ipdb
ipdb是pdb的增强版,提供更好的用户体验。
# 安装
pip install ipdb
# 使用
import ipdb
def debug_function():
x = 10
ipdb.set_trace()
y = x * 2
return y
四、远程调试
4.1 使用pdb远程调试
import pdb
import sys
class RemotePdb(pdb.Pdb):
def __init__(self, host='127.0.0.1', port=4444):
import socket
self.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, stdin=conn.makefile('r'), stdout=conn.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: 进入函数
- Shift+F8: 跳出函数
- F9: 继续执行
- Alt+F9: 运行到光标处
六、断言调试
6.1 使用assert
def divide(a, b):
assert b != 0, "除数不能为0"
assert isinstance(a, (int, float)), "a必须是数字"
assert isinstance(b, (int, float)), "b必须是数字"
return a / b
# 禁用断言(生产环境)
# python -O script.py
6.2 自定义断言函数
def debug_assert(condition, message="断言失败"):
if not condition:
import traceback
traceback.print_stack()
raise AssertionError(message)
七、日志调试
7.1 结构化日志
import logging
import json
class 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 logging
from contextvars import ContextVar
request_id = ContextVar('request_id', default=None)
class ContextFilter(logging.Filter):
def filter(self, record):
record.request_id = request_id.get()
return True
logger = logging.getLogger()
logger.addFilter(ContextFilter())
八、性能调试
8.1 使用cProfile
import cProfile
import pstats
def slow_function():
total = 0
for i in range(1000000):
total += i
return 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
# 使用装饰器
@profile
def slow_function():
total = 0
for i in range(1000000):
total += i
return total
# 运行
# kernprof -l -v script.py
8.3 使用timeit
import timeit
# 测量代码执行时间
time = timeit.timeit('sum(range(100))', number=10000)
print(f"执行时间: {time:.4f}秒")
# 比较不同实现
time1 = timeit.timeit('[i for i in range(100)]', number=10000)
time2 = timeit.timeit('list(range(100))', number=10000)
九、内存调试
9.1 使用tracemalloc
import 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_profiler
from memory_profiler import profile
@profile
def memory_intensive():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a
# 运行
# python -m memory_profiler script.py
十、调试装饰器
10.1 函数调用跟踪
import functools
def 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 result
return wrapper
@trace
def add(a, b):
return a + b
10.2 异常捕获装饰器
import functools
import traceback
def 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()
raise
return wrapper
十一、调试技巧
11.1 二分查找bug
# 注释掉一半代码,确定bug在哪一半
# 重复这个过程直到找到问题代码
11.2 橡皮鸭调试法
# 向别人(或橡皮鸭)解释代码
# 在解释过程中往往能发现问题
11.3 最小化重现
# 创建最小的可重现示例
def minimal_example():
# 只包含重现bug所需的最少代码
pass
11.4 检查假设
# 不要假设任何事情
# 验证每个假设
assert isinstance(data, list), f"data应该是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, to=None):
if to is None:
to = []
to.append(element)
return to
12.2 闭包中的变量
# 错误
functions = []
for i in range(5):
functions.append(lambda: i)
# 所有函数都返回4
# 正确
functions = []
for i in range(5):
functions.append(lambda x=i: x)
12.3 浅拷贝vs深拷贝
import copy
original = [[1, 2], [3, 4]]
# 浅拷贝
shallow = original.copy()
shallow[0][0] = 999
print(original) # [[999, 2], [3, 4]] - 被修改了!
# 深拷贝
deep = copy.deepcopy(original)
deep[0][0] = 999
print(original) # [[1, 2], [3, 4]] - 没有被修改
十三、调试工具集
13.1 icecream
# 安装
pip install icecream
from icecream import ic
def calculate(x, y):
ic(x, y) # 自动显示变量名和值
result = x + y
ic(result)
return result
13.2 pysnooper
# 安装
pip install pysnooper
import pysnooper
@pysnooper.snoop()
def complex_function(x):
y = x * 2
z = y + 10
return z
# 自动记录每一行的执行和变量变化
13.3 hunter
# 安装
pip install hunter
import hunter
# 跟踪所有函数调用
hunter.trace(module='mymodule')
十四、调试最佳实践
1. 重现bug
2. 理解预期行为
3. 隔离问题
4. 使用版本控制二分查找
5. 添加测试用例
6. 修复后验证
7. 记录解决方案
8. 清理调试代码
十五、调试检查清单
- [ ] 能稳定重现bug吗?
- [ ] 错误消息说了什么?
- [ ] 最近改了什么?
- [ ] 输入数据是什么?
- [ ] 预期输出是什么?
- [ ] 实际输出是什么?
- [ ] 有日志吗?
- [ ] 有测试吗?
- [ ] 其他人能重现吗?
十六、总结
调试是软件开发的重要技能。掌握多种调试工具和技巧,从简单的print到专业的调试器,从性能分析到内存跟踪,可以大大提高问题定位和解决的效率。记住,好的调试不仅是找到bug,更是理解代码的过程。
Python代码调试技巧
张小明
前端开发工程师
C# 生成命令行程序 将hex格式烧录程序转换成bin烧录格式
1.程序using System; using System.Collections.Generic; using System.IO; using System.Linq;namespace Hex2Bin {class Program{static void Main(string[] args){if (args.Length < 2 || args.Length > 3){ShowHelp();return;}string inputFile args[0];string outp…
PPTist:零安装在线PPT制作工具的完整指南
PPTist:零安装在线PPT制作工具的完整指南 【免费下载链接】PPTist PowerPoint-ist(/pauəpɔintist/), An online presentation application that replicates most of the commonly used features of MS PowerPoint, allowing for the editin…
从一道编程题看算法思维:如何用Java高效解决‘动物园栅栏’排队问题
从一道编程题看算法思维:如何用Java高效解决‘动物园栅栏’排队问题 当你在技术面试或算法竞赛中遇到看似简单的题目时,能否快速识别其中的关键约束条件并将其转化为高效的代码逻辑?"动物园栅栏"问题正是这样一个绝佳的教学案例&am…
大疆无人机固件自由下载:DankDroneDownloader完整使用指南
大疆无人机固件自由下载:DankDroneDownloader完整使用指南 【免费下载链接】DankDroneDownloader A Custom Firmware Download Tool for DJI Drones Written in C# 项目地址: https://gitcode.com/gh_mirrors/da/DankDroneDownloader 你是否曾因固件升级导致…
GPT-Image-2技术架构深度拆解:2026年图像生成模型全面解析
GPT-Image-2是OpenAI在2025年底推出的原生多模态图像生成模型,基于扩散Transformer(DiT)架构,深度集成于GPT-4o体系之中。它在文本渲染准确率(约92%)、空间推理能力和多轮编辑方面实现了显著提升࿰…
Python变量本质、命名规则与常量写法(破除新手认知误区)
博客摘要90%新手都误解了Python变量:变量不是装数据的盒子,只是贴在内存上的标签。本文从内存底层拆解变量赋值逻辑,区分硬性命名红线与PEP8规范,补齐Python无原生常量的替代写法,覆盖面试高频考点。一、变量底层本质&…