在工作中,你可以使用pdb查看你的代码错误。当你学习某个程序时,你可以可以使用pdb调试它的逻辑。当然你也可以选择其他的调试器,但是使用debugger是作为程序员必备的一项技能。pdb也是python的标准库的一部分。
开始吧!首先来打印一个变量的值
首先将下面的代码放在你想要打断点的地方
import pdb; pdb.set_trace()
当代码执行到这句话的时候,程序会停下来,等待你的指令。在(Pdb)上键入命令即可。另外,值得关注的是,从python3.7开始,有更方便的内置调试函数,breakpoint()。用这个会更加灵活,你可以通过调用它的API和环境变量PYTHONBREAKPOINT控制调试过程。如果你是3.7以上的版本推荐你使用这个。
当然你也可以不必要非得加上上面的调试语句才能进行调试,你可以加上-m pdb参数进行调试,如果你的应用需要参数,也可以正常加上参数。
python3 -m pdb app.py arg1 arg2
pdb有很多的调试命令,让我先用p来打印变量吧
运行下面这段代码,example1.py
#!/usr/bin/env python3
filename = __file__
import pdb; pdb.set_trace()
print(f'path = {filename}')
出现下面的结果
$ ./example1.py
> /code/example1.py(5)()
-> print(f'path = {filename}')
(Pdb)
键入p filename得到下面的结果
(Pdb) p filename
'./example1.py'
(Pdb)
可以使用ctrl+c或者q退出调试
打印表达式
请看下面的代码
#!/usr/bin/env python3
import os
def get_path(filename):
"""Return file's path or empty string if no path."""
head, tail = os.path.split(filename)
import pdb; pdb.set_trace()
return head
filename = __file__
print(f'path = {get_path(filename)}')
运行上面的代码,在命令提示行中依次输入下面的命令
(Pdb) ll
6 def get_path(filename):
7 """Return file's path or empty string if no path."""
8 head, tail = os.path.split(filename)
9 import pdb; pdb.set_trace()
10 -> return head
(Pdb) p filename
'./example2.py'
(Pdb) p head, tail
('.', 'example2.py')
(Pdb) p 'filename: ' + filename
'filename: ./example2.py'
(Pdb) p get_path
(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb)
你会发现你可以打印出所有有效的python表达式
单步调试n: 继续执行下一步
s:进到当前的执行到的函数内部去执行
执行下面的代码,examples3.py
#!/usr/bin/env python3
import os
def get_path(filename):
"""Return file's path or empty string if no path."""
head, tail = os.path.split(filename)
return head
filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')
看看n 和s 的效果,下面是n
$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) n
> /code/example3.py(15)()
-> print(f'path = {filename_path}')
(Pdb)
然后是s
$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb)
当使用s进到函数内部执行完之后,然后按原路返回原先的代码处
展示源码
使用ll可以查看你当前代码执行的上下文源码,运行下面的代码
$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
6 -> def get_path(filename):
7 """Return file's path or empty string if no path."""
8 head, tail = os.path.split(filename)
9 return head
(Pdb)
如果你要查看稍微短点的代码,可以直接使用l,但是一直用这个l会打印到下面的代码去,加上参数.就会一直展示当前代码的上下11行代码了。请看下面的使用
$ ./example3.py
> /code/example3.py(14)()
-> filename_path = get_path(filename)
(Pdb) l
9 return head
10
11
12 filename = __file__
13 import pdb; pdb.set_trace()
14 -> filename_path = get_path(filename)
15 print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
9 return head
10
11
12 filename = __file__
13 import pdb; pdb.set_trace()
14 -> filename_path = get_path(filename)
15 print(f'path = {filename_path}')
[EOF]
(Pdb)
断点
有时候你可能不想一步一步查看所有的代码,这时候断点就起作用了。断点的语法如下:
b(reak) [ ([filename:]lineno | function) [, condition] ]
你可以根据行号打断点,也可以根据代码逻辑大断点,请看下面的example4.py
#!/usr/bin/env python3
import util
filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')
还有一个文件util.py
def get_path(filename):
"""Return file's path or empty string if no path."""
import os
head, tail = os.path.split(filename)
return head
打个断点看看
$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb)
当前在examples4里面执行,断点打在util.py文件的第5行,按c之后代码直接跳到util.py的第5行继续执行。或者使用函数名打断点也是可以的
$ ./example4.py
> /code/example4.py(7)()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./example4.py'
(Pdb)
只键入一个b,不带参数,会列出断点列表
(Pdb) b
Num Type Disp Enb Where
1 breakpoint keep yes at /code/util.py:1
(Pdb)
可以根据前面的数值激活或者取消断点,请看下面的代码
(Pdb) disable 1
Disabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type Disp Enb Where
1 breakpoint keep no at /code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type Disp Enb Where
1 breakpoint keep yes at /code/util.py:1
(Pdb)
你也可以使用cl(ear)删除断点
cl(ear) filename:lineno
cl(ear) [bpnumber [bpnumber...]]
还有一种根据表达式设置断点的例子,没用过,不常用,这里不多做介绍了
查看调用棧
可以使用w查看调用棧,请看下面的例子example5.py
#!/usr/bin/env python3
import fileutil
def get_file_info(full_fname):
file_path = fileutil.get_path(full_fname)
return file_path
filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')
另一个文件fileutil.py
def get_path(fname):
"""Return file's path or empty string if no path."""
import os
import pdb; pdb.set_trace()
head, tail = os.path.split(fname)
return head
运行例子
$ ./example5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
/code/example5.py(12)()
-> filename_path = get_file_info(filename)
/code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb)
可以看到打印出了当前的调用棧,也就是进到这个函数的上一层调用,另外还有两个命令,一个u可以继续往调用棧上走,w是往调用棧下走
好了,平时常用的调试命令就介绍到这里了,你要是有什么不懂的,可以使用h 查看所有的命令列表