写Python代码时,逻辑错误和运行时异常几乎无法避免。相比在每一行都写print把变量打出来,使用专业的调试工具可以动态暂停程序、观察上下文并单步执行,从而精准定位问题根源。本文围绕标准库与常用实践,介绍在Python中调试代码的几种有效方式。

使用pdb进行交互式调试
pdb是Python内置的调试器,无需安装任何第三方包即可使用。它提供了一套类似GDB的命令,可以在程序执行到某处时停下来,检查局部变量、全局变量以及调用栈信息。最常见的做法是在代码中引入pdb模块并调用set_trace方法,程序运行到这里会进入交互模式。
下面是一段有逻辑错误的示例,我们在计算平均值时误把分母写成零,通过pdb可以清楚看到变量状态:
import pdb
def calc_average(nums):
total = 0
for n in nums:
total += n
# 故意写错的分母
pdb.set_trace()
avg = total / 0
return avg
result = calc_average([10, 20, 30])
print(result)
运行后程序会在set_trace处暂停,此时可以输入命令n执行下一行,输入p total打印total的值,输入l查看周围代码。如果发现分母应为len(nums),就能快速修正。pdb还支持b 行号设置断点、c继续运行、s进入函数内部等,熟练后调试效率很高。
pdb常用命令对照
| 命令 | 作用 |
|---|---|
| n | 执行下一行,不进入函数 |
| s | 单步进入函数调用 |
| c | 继续执行直到下一个断点 |
| p 变量名 | 打印变量当前值 |
| l | 列出当前执行位置附近的代码 |
| q | 退出调试器并终止程序 |
虽然pdb是命令行工具,但它对远程服务器上无图形界面的环境特别友好。当生产环境出现偶发错误,而又不能本地复现,通过ssh连上后使用pdb定位往往比加日志更快。
利用breakpoint函数快速插桩
从Python 3.7开始,标准库提供了内置函数breakpoint,它相当于pdb.set_trace的简化写法,而且可以通过环境变量PYTHONBREAKPOINT切换底层调试后端。这意味着以后想换用其他调试器,代码无需改动。
示例代码如下,逻辑是找出列表中第一个大于阈值的数:
def find_first_bigger(items, threshold):
for i, val in enumerate(items):
if val > threshold:
breakpoint()
return i
return -1
idx = find_first_bigger([3, 7, 12, 5], 6)
print(idx)
在支持的环境中,breakpoint会直接拉起pdb。如果设置了PYTHONBREAKPOINT=0,则该函数变成空操作,方便在正式环境一键关闭所有断点。相比以前要写import pdb; pdb.set_trace(),如今只写breakpoint更加简洁,也减少了误提交调试语句的概率。
不过要注意,breakpoint虽好,但提交到代码仓库前应当确认是否删掉,否则别人运行你的脚本会意外卡在调试界面。团队可借助预提交钩子扫描关键字来防止遗漏。
借助IDE图形化调试
如果你使用PyCharm、VS Code等现代编辑器,图形化调试比命令行直观很多。以VS Code为例,在行号左侧单击即可打红点设置断点,按F5启动调试,侧边栏实时显示变量、调用栈和监视表达式。对于复杂对象,还能展开属性树,不必手动敲打印命令。
图形化工具背后其实也是调用debugpy之类的适配器,与pdb原理相通,但降低了记忆命令的成本。下面展示一个在VS Code里调试类方法的场景:
class Counter:
def __init__(self):
self.count = 0
def add(self, step):
self.count += step
return self.count
c = Counter()
c.add(5)
c.add(3)
在add方法内部设断点后,每次调用都能看到self.count的变化过程。如果怀疑某次调用传参异常,可在监视窗口写step > 10这样的表达式,调试器会在条件满足时暂停。这种条件断点功能在循环次数极多时尤为实用,避免手动一直按继续。
命令行与图形界面如何选择
- 服务器无界面场景:优先pdb或breakpoint,通过终端交互。
- 本地开发阶段:用IDE图形调试,节省时间且不易看错变量。
- 临时排查脚本:直接加breakpoint,事后再清理。
两种方式并非互斥,很多开发者本地用IDE、线上用pdb,根据环境灵活切换即可。
通过日志与异常追踪辅助调试
调试并不总意味着暂停程序。有些问题只在特定请求或并发下出现,这时候用logging模块输出带时间戳的日志,比断点更合适。同时,遇到未捕获异常时,Python默认的traceback已经指出出错文件和行号,结合pdb的post_mortem功能还能事后调查。
以下示例演示如何用pdb.post_mortem在程序崩溃后进入现场:
import pdb, traceback
def risky():
return 1 / 0
try:
risky()
except Exception:
traceback.print_exc()
pdb.post_mortem()
执行后异常被打印,并立即进入pdb界面,你可以检查崩溃那一刻的局部变量。对于难以复现的bug,这种“死后验尸”的方式非常有价值。此外,建议在关键路径用logging.info记录输入输出,而不是零散的print,这样既方便调试,也利于上线后排查。
综合来看,Python调试的手段非常丰富。从标准库pdb、breakpoint到IDE图形工具,再到日志与异常分析,各自覆盖不同场景。理解其底层机制后,遇到问题时便能挑最合适的那一种,而不是只会盲目加打印。