Fancy‘s Technology Blog

Fancy的技术博客
tc sc en

Python标准库 —— pdb调试工具使用

2020-02-20 Code Fancy

日常DEBUG中,我常用traceback以及Pycharm的无脑断点调试,直到有一天线上在高并发下,僵尸进程暴涨导致卡死,无法精准的定位某些异常时,掌握Python3自带的pdb标准库或ipdb进行单步调试就显得十分必要。

  • Q&A:为何不是Pycharm的远程调试?

    • 当时的我只用了Pycharm SFTP上传代码和本地调试,至于远程Debug,就像一开始从Vim转到Pycharm一样,感觉多学点积累并不是坏事,后来自己在空闲的时候补习实践了更多Pycharm的进阶,后期进行Pycharm相关的积累记录时会进行总结比较,熟练之后,pdb还是很高效的一种debug方式。
  • ipdb

    • pdb时Python标准库提供的调试器,而ipdb则需要pip install,基本功能相同,相当于Ipython + pdb,增强型的pdb多了代码补全以及语法高亮种种。

PDB全部 Debugger 命令

重点关注加粗部分即可,有需要再来速查

命令 解释
h(elp) 帮助
w(here) 打印当前堆栈跟踪位置
d(own) 执行跳转到在当前堆栈的下一层
u(p) 执行跳转到当前堆栈的上一层
b(reak) 设置断点,line_no:当前脚本的line_no行添加断点 filename:line_no:脚本filename的line_no行添加断点 function:在函数function的第一条可执行语句处添加断点
tbreak 临时断点,运行一次即删除,参数与 break
cl(ear) 清除断点,默认清除所有断点 bpnumber1 bpnumber2… 清除断点号为bpnumber1,bpnumber2…的断点 lineno 清除当前脚本lineno行的断点 filename:line_no 清除脚本filename的line_no行的断点
disable/enable 停用但不清除/激活断点
ignore 忽略断点
condition 设置条件断点
commands 指定断点号 bpnumber 的命令
s(tep) 继续执行,进入函数或当前函数
n(ext) 继续执行,当前函数下一行
unt(il) 默认执行到更大一个行数(跳出循环),支持指定行数
r(eturn) 执行代码直到从当前函数返回
c(ont(inue)) 继续执行程序直到下一条断点
j(ump) 跳转到指定的行数
l(ist) 查看当前行的代码段
ll 列出所有代码,行标记为列表
a(rgs) 打印当前函数的参数
p 打印变量(expression)的值
pp 以一种更漂亮的方式打印变量(expression)的值
whatis 打印变量(expression)的类型
source 显示尝试获取的源代码
display 显示已更改的表达式的值
undisplay 不显示已更改的表达式的值
interact 启动交互式解释器
alias 创建执行命令的别名
unalias 删除别名
! 后面跟单行语句,可以直接在当前堆栈框架执行
run 重新运行程序
restart run的别名
q(uit) 中止并退出
debug 输入递归调试器,用于遍历参数。
retval 打印函数最后一次返回值

根据应用场景选择不同的方式:

  • pdb启动Python文件
$ python3 -m pdb myscript.py
  • 内部调用
# !usr/bin/python
# -*- coding: utf-8 -*-

import pdb
import time


def countit(n):
    s=0
    for i in range(n):
        time.sleep(1)
        pdb.set_trace()
        print(s)
        s += i
        
if __name__ == "__main__":
    seconds = 10
    countit(seconds)
$ python3 myscript.py

两种方式没有实际上的区别,根据文件大小以及应用环境灵活运用即可。

启动调试器后,如第一种方式pdb会在每一步停止,第二种则在指定的断点处停止,pdb显示断点下一行,便可以使用上部表格提供的命令,进行断点调试

$ python3 -m pdb test.py
> /Users/dilophosaurus/PycharmProjects/untitled1/test.py(1)<module>()
-> print(s)
(Pdb)

堆栈跟踪

$ python3 myscript.py
> /Users/fancy/test_pdb.py(13)countit()
-> print(s)
(Pdb) w         # 显示当前行及堆栈位置
  /Users/fancy/test_pdb.py(18)<module>()
-> countit(seconds)
> /Users/fancy/test_pdb.py(13)countit()
-> print(s)
(Pdb) l         # 显示代码上下文
  8  	def countit(n):
  9  	    s=0
 10  	    for i in range(n):
 11  	        time.sleep(1)
 12  	        pdb.set_trace()
 13  ->	        print(s)
 14  	        s += i
 15
 16  	if __name__ == "__main__":
 17  	    seconds = 10
 18  	    countit(seconds)
(Pdb) n         # 向下单步执行
0
> /Users/fancy/test_pdb.py(14)countit()
-> s += i
(Pdb) up        # 向上调用堆栈
> /Users/fancy/test_pdb.py(18)<module>()
-> countit(seconds)
(Pdb) down      # 向下调用堆栈
> /Users/fancy/test_pdb.py(14)countit()
-> s += i
(Pdb) c         # 继续执行程序直到下一条断点  
> /Users/dilophosaurus/PycharmProjects/untitled1/test_pdb.py(12)countit()
-> pdb.set_trace()
(Pdb) s         # 继续执行,区别于n(可见会进入调用函数内部)
--Call--
> /usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pdb.py(1604)set_trace()
-> def set_trace(*, header=None):
(Pdb) a        # 打印**当前**函数的参数
header = None 
(Pdb) c
0
> /Users/dilophosaurus/PycharmProjects/untitled1/test_pdb.py(13)countit()
-> print(s)
(Pdb) pp s     # `s`作为pdb保留关键字(step),必须用过`p`或者`pp`进行显示
1
(Pdb) h        # 回顾下我们所使用的命令

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt
alias  clear      disable  ignore    longlist  r        source   until
args   commands   display  interact  n         restart  step     up
b      condition  down     j         next      return   tbreak   w
break  cont       enable   jump      p         retval   u        whatis
bt     continue   exit     l         pp        run      unalias  where

Miscellaneous help topics:
==========================
exec  pdb

(Pdb)

更多请参考官方文档

comments powered by Disqus