用 pdb 系统排查 Python 问题:变量、单步执行与条件断点

2026-09-12 19 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

当 Python 程序出现异常时,直接添加 print() 往往只能看到零散的中间结果。pdb 是 Python 内置调试器,可以暂停程序、检查变量、逐行执行代码、设置条件断点,并结合调用栈定位问题。掌握这些基本操作后,调试过程会从“猜哪里错了”变成“观察程序正在做什么”。

从一个可运行的例子开始

下面的程序计算购物车总价,并根据折扣规则生成最终金额。示例故意保留一个容易观察的调试位置:折扣被应用在每件商品上,而业务规则可能要求对总价统一打折。

将代码保存为 debug_cart.py,然后运行它:

from dataclasses import dataclass


@dataclass
class Item:
    name: str
    price: float
    quantity: int


def calculate_total(items: list[Item], discount: float) -> float:
    total = 0.0

    for item in items:
        subtotal = item.price * item.quantity
        total += subtotal

        if discount > 0:
            total -= subtotal * discount

    return round(total, 2)


cart = [
    Item("Keyboard", 299.0, 1),
    Item("Mouse", 99.0, 2),
]

print(calculate_total(cart, 0.1))

使用 pdb 运行整个脚本:

python -m pdb debug_cart.py

进入调试器后,可以输入以下命令:

(Pdb) break calculate_total
(Pdb) continue
(Pdb) list
(Pdb) next
(Pdb) p items
(Pdb) p discount
(Pdb) p total
(Pdb) continue

这些命令分别用于设置函数断点、继续运行、查看当前代码、执行下一行,以及打印变量。pprint 的简写,适合快速检查表达式的值。

掌握四类高频操作

1. 单步执行并观察状态

常用的执行命令包括:

  • nnext:执行当前行,停在当前栈帧的下一行。
  • sstep:执行当前行;如果遇到函数调用,会进入函数内部。
  • rreturn:继续运行,直到当前函数返回。
  • ccontinue:继续运行,直到遇到下一个断点或程序结束。
  • llist:显示当前代码附近的源代码。
  • qquit:退出调试器。

调试循环时,可以在每次 next 后检查 itemsubtotaltotal

(Pdb) p item.name
'Keyboard'
(Pdb) p subtotal
299.0
(Pdb) p total
269.1
(Pdb) next
(Pdb) p item.name
'Mouse'
(Pdb) p subtotal
198.0
(Pdb) p total
447.3

这样可以清楚看到每一次循环如何改变累计值。调试器中的表达式也可以直接计算:

(Pdb) p item.price * item.quantity
(Pdb) p [item.name for item in items]

如果需要执行一段 Python 语句,可以使用 ! 前缀。例如,临时修改当前作用域中的变量:

(Pdb) !discount = 0
(Pdb) p discount
0

这类修改只影响当前调试进程,适合验证假设,不会改写源文件。

2. 使用断点缩小范围

除了按行号设置断点,也可以按函数名设置:

(Pdb) break calculate_total

如果已经停在某个位置,可以用 break 设置当前文件的行号断点:

(Pdb) break 16

断点太多时,可以查看和删除它们:

(Pdb) break
(Pdb) clear 1

调试大型循环时,普通断点可能会反复暂停。条件断点能让程序只在满足条件时停下。例如,只检查鼠标这一项:

(Pdb) break 14, item.name == "Mouse"
(Pdb) continue

也可以按数值条件暂停:

(Pdb) break 15, total < 0

条件表达式应当使用当前作用域中已经存在的变量,否则断点触发时可能产生额外错误或无法按预期工作。

用调用栈理解异常发生在哪里

当程序因为异常暂停,pdb 可以显示当前调用栈。输入 wherew

(Pdb) where

调用栈会列出从外层调用到当前执行位置的路径。查看栈帧时,可以使用:

  • up:移动到调用当前函数的上一层栈帧。
  • down:移动到下一层栈帧。
  • args:显示当前函数的参数。
  • p expression:查看当前栈帧中的变量或表达式。

例如,当一个服务函数调用 calculate_total() 时,where 能帮助你判断错误发生在请求处理层、业务函数,还是更深的辅助函数中。不要只盯着最后一行异常信息;调用栈同时提供了“错误在哪里抛出”和“程序是怎样走到这里的”两种信息。

在代码中嵌入断点

如果只想调试某个明确位置,可以在代码中加入 breakpoint()

def calculate_total(items: list[Item], discount: float) -> float:
    total = 0.0

    for item in items:
        subtotal = item.price * item.quantity
        breakpoint()
        total += subtotal

        if discount > 0:
            total -= subtotal * discount

    return round(total, 2)

然后正常运行:

python debug_cart.py

执行到 breakpoint() 时,Python 会进入调试器。这个方式适合临时检查局部逻辑;调试完成后应删除断点,或者在需要长期保留时显式使用更适合项目的调试配置。

一份实用的 pdb 检查清单

调试一个未知问题时,可以按下面的顺序操作:

  1. 在怀疑的函数入口设置断点:break function_name
  2. continue 运行到断点,确认传入参数:argsp variable
  3. list 查看上下文,避免只看当前一行。
  4. next 逐步执行,用 p 检查关键状态变化。
  5. 对循环或请求数据设置条件断点,减少无关暂停。
  6. 遇到异常时执行 where,再用 updown 检查调用链。
  7. quit 退出,并删除不再需要的代码断点。

pdb 的价值不在于命令数量多,而在于它让调试动作变得可重复:暂停一个明确位置,观察真实变量状态,验证一个具体假设,再决定下一步。对于简单脚本、数据处理程序和服务端业务函数,这套内置工具已经足以覆盖大量日常排错场景。


相关推荐