PDB 调试代码
这里的 pdb 可不是「可插拔数据库」,而是 Python 自带的一个调试工具,有点类似于 GNU 的 gdb。
gdb 调试 C/C++ 的代码, 而 pdb(The Python Debugger) 则用来调试 Python 代码。
pdb 至少有三种使用方式去调用,每种调用方式对应着不同的场景,但是进入调试模式后的操作逻辑相似。
进入到调试界面
通过 -m pdb 调用
针对一些小型的,只有单个文件的模块(即*.py文件),可以使用
python -m pdb my_module.py
的方式进入到 pdb 调试模式。
# file name: test.py
def sum(a, b):
print a
print b
return a+b
if __name__ == "__main__":
a = 100
b = 200
c = sum(a, b)
print c
通过 -m pdb
调用会停住在「模块文件中除注释外的第一行」,在这里就是函数签名。
$ python -m pdb test.py
> /private/tmp/test.py(2)<module>()
-> def sum(a, b):
(Pdb)
通过 pdb.run() 调用
针对交互式终端(即终端输入 python
进入的界面),可以使用 pdb.run(func_name(**args))
就可以调试当前终端的某些函数或者类,但不常用。
$ python
Python 2.7.15 (default, Sep 18 2018, 20:16:18)
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def sum(a, b):
... print a
... print b
... return a + b
...
>>> import pdb
>>> pdb.run('sum(100, 200)')
> <string>(1)<module>()->None
(Pdb) s
--Call--
> <stdin>(1)sum()
(Pdb)
可以通过上面代码的倒数第二行看出,通过 pdb 进入到了 「当前 python shell 的 sum() 里」,但是 pdb 的很多功能并不如预期,所以很少用这种方法。
通过pdb.set_trace()
在大型项目中,通常会有很多模块文件,也有复杂的调用关系,这时再用 -m pdb
的方式就太低效了。这时可以使用 pdb.set_trace()
的方法做定位。程序会自动运行,直到运行到 set_trace() 的地方。
# file name: test.py
import pdb
def sum(a, b):
print a
print b
return a+b
if __name__ == "__main__":
a = 100
pdb.set_trace()
b = 200
c = sum(a, b)
print c
$ python
> /private/tmp/test.py(12)<module>()
-> b = 200
(Pdb)
可以看到直接运行到了 pdb.set_trace()
的下一行。
PDB调试指令
先来看一张表格。
命令 | 作用 | 解释 |
---|---|---|
h(elp) | 帮助 | 如果没有参数,则打印可用命令列表。使用命令作为参数,打印有关该命令的帮助。 |
n(ext) | 下一行 | 运行到下一行,把任何调用都视为一行(即不进入函数体) |
s(tep) | 执行当前行,进入下一行 | 和 n(ext) 不同,它会执行当前行,并且有调用会进入到函数体内 |
l(ist) | 查看当前文件源代码 | 可以查看当前文件的源代码,有箭头指向当前行 |
a(rgs) | 查看当前函数的参数 | 可以查看当前函数接受的参数以及其值 |
c(ontinue) | 继续执行 | 会执行到下一个断点处,如果没有断点,就一直运行到结束 |
p expression | 执行命令 | 这个参数在调试中非常有用,它可以打印变量,也可以执行语句,执行一些内建函数,例如 len(), dir(), __dict__ 等等 |
b(reak) | 打断点 | 常用的打断点方式,后接行数或者函数名,会在对应行数和函数的第一行打上断点,可以 c(ontinue) 直接到断点处 |
cl(ear) | 清空断点 | 清空 b(reak) 所打的断点 |
q(uit) | 退出 | 放弃治疗,退出调试模式 |
常用的在上面的表格中了,当然还有更多的用法可以参照 26.2. pdb — The Python Debugger 。
下面会附上一些渣翻。
h(elp) [command]
如果没有参数则打印可用命令列表。
如果有参数则打印参数对应的命令帮助。
如果参数为 pdb
则展示关于 pdb 完整的文档。
w(here)
打印堆栈跟踪,最新的调用栈在底部。箭头表示当前执行语句,通过它确认命令的上下文。
d(own)
将当前帧在堆栈跟踪中向下移动一级(到较新的帧),(一般配合up使用,但没有up使用的多。)(一般可以通过step进入到被调函数里,然后通过up/down进行移动)。
u(p)
将当前帧在堆栈跟踪中向上移动一级(到较旧的帧)(也就是调用的地方)。
b(reak) [[filename:]lineno | function[, condition]]
用于设置断点。通过 lineno 参数,在当前文件设置一个断点(准确说是当前pdb所断点的地方对应的文件)。通过 function 参数,在函数的第一个可执行语句设置一个断点。lineno 可以带有文件名和冒号前缀,以指定另一个文件中的断点(可能还有尚未加载的断点)。在 sys.path 上搜索该文件。请注意,为每个断点分配一个所有其他断点命令所引用的数字。
如果存在第二个参数,则它是一个表达式,在断点被接受之前必须求值为true。
如果没有参数,则列出所有中断,包括每个断点,断点被触发的次数,当前忽略计数以及相关条件(如果有)。
tbreak [[filename:]lineno | function[, condition]]
临时断点,在第一次被触发时自动删除。 参数与break相同。
cl(ear) [filename:lineno | bpnumber [bpnumber …]]
清空断点。使用 filename:lineno 的参数,清除此行的断点。使用空格隔开的 断点编号列表,清除这些断点。倘若没有参数,则会在确认后移除所有断点。
disable [bpnumber [bpnumber …]]
使用空格隔开的 断点编号列表,禁用这些断点。禁用断点意味着它不能导致程序停止执行,但与清除断点不同,它们可以保存在 断点列表 中,可以重新启用。
enable [bpnumber [bpnumber …]]
使用空格隔开的 断点编号列表,启用这些断点。
ignore bpnumber [count]
设置给定断点号的忽略计数。 如果省略count,则忽略计数设置为0。当忽略计数为零时,断点将变为活动状态。 当非零时,每次到达断点时计数都会递减,并且断点未被禁用且任何关联条件的计算结果为true。
condition bpnumber [condition]
Condition是一个表达式,在断点被接受之前必须求值为true。 如果条件不存在,则删除任何现有条件; 即,断点是无条件的。
commands [bpnumber]
Specify a list of commands for breakpoint number bpnumber. The commands themselves appear on the following lines. Type a line containing just ‘end’ to terminate the commands. An example:
(Pdb) commands 1
(com) print some_variable
(com) end
(Pdb)
To remove all commands from a breakpoint, type commands and follow it immediately with end; that is, give no commands.
With no bpnumber argument, commands refers to the last breakpoint set.
You can use breakpoint commands to start your program up again. Simply use the continue command, or step, or any other command that resumes execution.
Specifying any command resuming execution (currently continue, step, next, return, jump, quit and their abbreviations) terminates the command list (as if that command was immediately followed by end). This is because any time you resume execution (even with a simple next or step), you may encounter another breakpoint—which could have its own command list, leading to ambiguities about which list to execute.
If you use the ‘silent’ command in the command list, the usual message about stopping at a breakpoint is not printed. This may be desirable for breakpoints that are to print a specific message and then continue. If none of the other commands print anything, you see no sign that the breakpoint was reached.
s(tep)
执行当前行,在第一个可能的场合停止(在被调用的函数中或在当前函数的下一行中停止)。(意味着可以进入到底层非自己写的代码中。)
n(ext)
继续执行,直到达到当前函数中的下一行或返回。(next 和 step 之间的区别在于 step 在被调用函数内停止,而 next 以(几乎)全速执行被调用函数,仅停止在当前函数的下一行。)(可以理解为粗粒度的下一步,调用过程不管。)
unt(il)
继续执行,直到达到行号大于当前行的行或从当前帧返回。
r(eturn)
继续执行,直到当前函数返回。
c(ont(inue))
继续执行,仅在遇到断点时停止。
j(ump) lineno
设置将要执行的下一行。 仅适用于最底部的框架。 这使您可以跳回并再次执行代码,或者跳转到跳过您不想运行的代码。
应该注意的是,并非所有跳转都是允许的 - 例如,不可能跳转到for循环的中间或跳出finally子句。
l(ist) [first[, last]]
列出当前文件的源代码。如果不指定参数,请在 当前运行行 上下各展示五行或继续上一个列表。使用一个参数,在 指定行 上下各展示武行。使用两个参数,列出给定范围(从first行到last行);如果第二个参数小于第一个参数,则将其解释为计数(从first行到first+last行)。
a(rgs)
打印当前函数的参数列表。
p expression
执行当前上下文中的表达式并打印其值。
也可以使用print打印表达式,但它不是调试器命令 - 这将执行Python print语句。
pp expression
与p命令一样,除了表达式的值使用pprint模块进行漂亮打印。
alias [name [command]]
Creates an alias called name that executes command. The command must not be enclosed in quotes. Replaceable parameters can be indicated by %1, %2, and so on, while %* is replaced by all the parameters. If no command is given, the current alias for name is shown. If no arguments are given, all aliases are listed.
Aliases may be nested and can contain anything that can be legally typed at the pdb prompt. Note that internal pdb commands can be overridden by aliases. Such a command is then hidden until the alias is removed. Aliasing is recursively applied to the first word of the command line; all other words in the line are left alone.
As an example, here are two useful aliases (especially when placed in the .pdbrc file):
#Print instance variables (usage "pi classInst")
alias pi for k in %1.__dict__.keys(): print "%1.",k,"=",%1.__dict__[k]
#Print instance variables in self
alias ps pi self
unalias name
Deletes the specified alias.
[!]statement
在当前堆栈帧的上下文中执行(一行)语句。除非语句的第一个单词类似于调试器命令,否则可以省略感叹号。要设置全局变量,可以在赋值命令的前面添加一个全局命令,例如:
(Pdb) global list_options; list_options = ['-l']
(Pdb)
run [args …]
重新启动调试的Python程序。 如果提供了参数,则使用“shlex”进行拆分,并将结果用作新的sys.argv。 保留历史记录,断点,操作和调试器选项。 “restart”是“run”的别名。
q(uit)
退出调试器。 正在执行的程序被中止。