介绍
在软件开发中,调试是寻找并解决阻止软件正确运行的问题的过程。
Python 调试器为 Python 程序提供一个调试环境,它支持设置条件间断点,一次通过源代码一个行,堆栈检查等。
前提条件
如果您没有设置编程环境,您可以参考本地编程环境的安装和安装指南(https://www.digitalocean.com/community/tutorial_series/how-to-install-and-set-up-a-local-programming-environment-for-python-3)或适用于您的操作系统(Ubuntu, CentOS, Debian 等)的编程环境(https://www.digitalocean.com/community/tutorial_collections/how-to-install-python-3-and-set-up-a-programming-environment)。
与 Python Debugger 互动工作
Python 调试器作为标准 Python 发行的一部分,作为一个称为 pdb
的模块。 调试器也是可扩展的,并且被定义为类 Pdb
. 你可以阅读 官方文档 pdb
来了解更多。
<$>[info]
Info: 要跟进本教程中的示例代码,请在本地系统上运行python3
命令,打开Python互动壳。
我们将开始使用一个短程序,其中有两个全球变量(LINK0),一个函数(LINK1)创建一个嵌入式循环(LINK2)),以及一个叫做nested_loop()
函数的ifname__ ==
main`的构造。
1[label looping.py]
2num_list = [500, 600, 700]
3alpha_list = ['x', 'y', 'z']
4
5def nested_loop():
6 for number in num_list:
7 print(number)
8 for letter in alpha_list:
9 print(letter)
10
11if __name__ == '__main__':
12 nested_loop()
我们现在可以通过Python调试器运行这个程序,使用以下命令:
1python -m pdb looping.py
m
命令行旗将为您导入任何Python模块,并将其作为脚本运行,在这种情况下,我们正在导入和运行pdb
模块,我们将其传入命令,如上所示。
运行此命令后,您将收到以下输出:
1[secondary_label Output]
2> /Users/sammy/looping.py(1)<module>()
3-> num_list = [500, 600, 700]
4(Pdb)
在输出中,第一个行包含当前模块名称(如<module>
所示)与目录路径,以及接下来的印刷行号(在这种情况下它是1
,但如果有评论或其他非可执行的行,它可能是一个更高的数字)。第二行显示在这里执行的当前源代码行,因为pdb
提供了一个交互式控制台来调试。你可以使用命令帮助
来学习其命令,并帮助命令
来了解更多关于特定的命令。
Python 调试器将在程序结束时自动重新启动,只要您想要离开pdb
控制台,请输入quit
或exit
命令,如果您想在程序中的任何地方明确重新启动程序,则可以使用运行
命令。
使用调试器来移动一个程序
在Python调试器中使用程序时,您可能会使用列表
,步骤
和下一步
命令来移动您的代码。
在壳中,我们可以输入命令列表
,以便在当前行周围获得背景。从我们上面显示的程序looping.py
的第一个行 - num_list = [500, 600, 700]
- 这将看起来如下:
1(Pdb) list
2 1 -> num_list = [500, 600, 700]
3 2 alpha_list = ['x', 'y', 'z']
4 3
5 4
6 5 def nested_loop():
7 6 for number in num_list:
8 7 print(number)
9 8 for letter in alpha_list:
10 9 print(letter)
11 10
12 11 if __name__ == '__main__':
13(Pdb)
目前的行是用字符 ->
表示的,这在我们的情况下是程序文件的第一行。
由于这是一个相对较短的程序,我们将几乎所有的程序带回列表
命令. 没有提供参数,列表
命令提供了当前行周围的11行,但您也可以指定哪些行要包含,如下:
1(Pdb) list 3, 7
2 3
3 4
4 5 def nested_loop():
5 6 for number in num_list:
6 7 print(number)
7(Pdb)
在这里,我们要求使用命令列表3、7
来显示3至7行。
要通过程序行后行移动,我们可以使用步骤
或下一步
:
1(Pdb) step
2> /Users/sammy/looping.py(2)<module>()
3-> alpha_list = ['x', 'y', 'z']
4(Pdb)
1(Pdb) next
2> /Users/sammy/looping.py(2)<module>()
3-> alpha_list = ['x', 'y', 'z']
4(Pdb)
步骤
和下一步
之间的区别在于步骤
会停留在一个被称为函数内,而下一步
会执行被称为函数的函数,只停留在当前函数的下一行。
步骤
命令将在循环中迭代,一旦它进入函数的运行,准确地显示循环正在做什么,因为它首先会打印一个数字以打印(数字)
,然后通过打印字母以打印(字母)
,返回数字等:
1(Pdb) step
2> /Users/sammy/looping.py(5)<module>()
3-> def nested_loop():
4(Pdb) step
5> /Users/sammy/looping.py(11)<module>()
6-> if __name__ == '__main__':
7(Pdb) step
8> /Users/sammy/looping.py(12)<module>()
9-> nested_loop()
10(Pdb) step
11--Call--
12> /Users/sammy/looping.py(5)nested_loop()
13-> def nested_loop():
14(Pdb) step
15> /Users/sammy/looping.py(6)nested_loop()
16-> for number in num_list:
17(Pdb) step
18> /Users/sammy/looping.py(7)nested_loop()
19-> print(number)
20(Pdb) step
21500
22> /Users/sammy/looping.py(8)nested_loop()
23-> for letter in alpha_list:
24(Pdb) step
25> /Users/sammy/looping.py(9)nested_loop()
26-> print(letter)
27(Pdb) step
28x
29> /Users/sammy/looping.py(8)nested_loop()
30-> for letter in alpha_list:
31(Pdb) step
32> /Users/sammy/looping.py(9)nested_loop()
33-> print(letter)
34(Pdb) step
35y
36> /Users/sammy/looping.py(8)nested_loop()
37-> for letter in alpha_list:
38(Pdb)
相反,下一步
命令将执行整个函数,而不会显示逐步过程。
1python -m pdb looping.py
现在我们可以使用下一步
命令:
1(Pdb) next
2> /Users/sammy/looping.py(5)<module>()
3-> def nested_loop():
4(Pdb) next
5> /Users/sammy/looping.py(11)<module>()
6-> if __name__ == '__main__':
7(Pdb) next
8> /Users/sammy/looping.py(12)<module>()
9-> nested_loop()
10(Pdb) next
11500
12x
13y
14z
15600
16x
17y
18z
19700
20x
21y
22z
23--Return--
24> /Users/sammy/looping.py(12)<module>()->None
25-> nested_loop()
26(Pdb)
当您通过代码时,您可能想要检查传递给变量的值,您可以使用 pp
命令来执行,该命令将使用 pprint
模块打印表达式的值:
1(Pdb) pp num_list
2[500, 600, 700]
3(Pdb)
pdb
中的大多数命令都有更短的名称,而step
中的短命令是s
,而next
中的命令是n
。help
命令会列出可用的名称。
突破点
您通常会使用比上面的示例更大的程序,所以您可能希望查看特定函数或行,而不是通过整个程序。
当您插入分割点时,调试器将给它分配一个数字. 分割点分配的数字是从数字1开始的连续整数,您可以在处理分割点时参考。
可以按以下示例按<program_file>:<line_number>
的语法在某些行数中放置分割点:
1(Pdb) break looping.py:5
2Breakpoint 1 at /Users/sammy/looping.py:5
3(Pdb)
键入清晰
,然后键入y
,以删除所有当前断片。
1(Pdb) break looping.nested_loop
2Breakpoint 1 at /Users/sammy/looping.py:5
3(Pdb)
若要删除当前断层,请键入清晰
,然后键入y
。
1(Pdb) break looping.py:7, number > 500
2Breakpoint 1 at /Users/sammy/looping.py:7
3(Pdb)
现在,如果我们发出继续
命令,那么当数
x被评估为大于500时(即当它在外循环的第二次迭代中设置为600时),程序会被打破:
1(Pdb) continue
2500
3x
4y
5z
6> /Users/sammy/looping.py(7)nested_loop()
7-> print(number)
8(Pdb)
若要查看当前设置为运行的分裂点的列表,请使用命令分裂
而无需任何参数,您将收到您已设置的分裂点(s)的特性信息:
1(Pdb) break
2Num Type Disp Enb Where
31 breakpoint keep yes at /Users/sammy/looping.py:7
4 stop only if number > 500
5 breakpoint already hit 2 times
6(Pdb)
我们还可以用命令禁用
和断点的号码禁用一个断点,在这个会话中,我们添加了另一个断点,然后禁用了第一个:
1(Pdb) break looping.py:11
2Breakpoint 2 at /Users/sammy/looping.py:11
3(Pdb) disable 1
4Disabled breakpoint 1 at /Users/sammy/looping.py:7
5(Pdb) break
6Num Type Disp Enb Where
71 breakpoint keep no at /Users/sammy/looping.py:7
8 stop only if number > 500
9 breakpoint already hit 2 times
102 breakpoint keep yes at /Users/sammy/looping.py:11
11(Pdb)
要启用分割点,使用启用
命令,并完全删除分割点,使用清晰
命令:
1(Pdb) enable 1
2Enabled breakpoint 1 at /Users/sammy/looping.py:7
3(Pdb) clear 2
4Deleted breakpoint 2 at /Users/sammy/looping.py:11
5(Pdb)
一些额外的功能包括通过忽略
命令(如在忽略1
中)在程序的当前迭代中忽略中断点,触发在命令
命令中断点发生的操作(如在命令1
中),并创建临时断点,在程序执行第一次触及命令tbreak
的点时自动清除(例如,在3行中暂时断点时,您可以键入tbreak 3
)。
将pdb
集成到程序中
您可以通过导入pdb
模块并在您希望会话开始的行上添加pdb
函数pdb.set_trace()
。
在我们上面的样本程序中,我们将添加导入
陈述和我们想进入调试器的函数。
1# Import pdb module
2import pdb
3
4num_list = [500, 600, 700]
5alpha_list = ['x', 'y', 'z']
6
7def nested_loop():
8 for number in num_list:
9 print(number)
10
11 # Trigger debugger at this line
12 pdb.set_trace()
13 for letter in alpha_list:
14 print(letter)
15
16if __name__ == '__main__':
17 nested_loop()
通过将调试器添加到您的代码中,您不需要以特殊的方式启动程序,也不需要记住设置突破点。
导入pdb
模块并运行pdb.set_trace()
函数允许您像往常一样启动程序并运行调试程序。
修改程序执行流程
Python 调试器允许您在运行时使用跳跃
命令更改程序的流程,这允许您跳前阻止某些代码运行,或允许您向后运行代码。
我们将使用一个小程序创建一个包含在字符串 `sammy = "sammy" 的字母列表:
1[label letter_list.py]
2def print_sammy():
3 sammy_list = []
4 sammy = "sammy"
5 for letter in sammy:
6 sammy_list.append(letter)
7 print(sammy_list)
8
9if __name__ == "__main__":
10 print_sammy()
如果我们以python letter_list.py
命令正常运行该程序,我们将收到以下输出:
1[secondary_label Output]
2['s']
3['s', 'a']
4['s', 'a', 'm']
5['s', 'a', 'm', 'm']
6['s', 'a', 'm', 'm', 'y']
使用 Python 调试程序,让我们看看我们如何通过在第一个循环后先 跳进来改变执行:当我们这样做时,我们会注意到 for
loop的中断:
1python -m pdb letter_list.py
1> /Users/sammy/letter_list.py(1)<module>()
2-> def print_sammy():
3(Pdb) list
4 1 -> def print_sammy():
5 2 sammy_list = []
6 3 sammy = "sammy"
7 4 for letter in sammy:
8 5 sammy_list.append(letter)
9 6 print(sammy_list)
10 7
11 8 if __name__ == "__main__":
12 9 print_sammy()
13 10
14 11
15(Pdb) break 5
16Breakpoint 1 at /Users/sammy/letter_list.py:5
17(Pdb) continue
18> /Users/sammy/letter_list.py(5)print_sammy()
19-> sammy_list.append(letter)
20(Pdb) pp letter
21's'
22(Pdb) continue
23['s']
24> /Users/sammy/letter_list.py(5)print_sammy()
25-> sammy_list.append(letter)
26(Pdb) jump 6
27> /Users/sammy/letter_list.py(6)print_sammy()
28-> print(sammy_list)
29(Pdb) pp letter
30'a'
31(Pdb) disable 1
32Disabled breakpoint 1 at /Users/sammy/letter_list.py:5
33(Pdb) continue
34['s']
35['s', 'm']
36['s', 'm', 'm']
37['s', 'm', 'm', 'y']
上面的调试会停在第5行以防止代码继续运行,然后继续通过代码(以及打印一些字母
值来显示正在发生的事情)。接下来,我们使用跳跃
命令跳到第6行。在这一点上,变量字母
被设置为等于字符串a
,但我们跳过代码添加到列表sammy_list
。
接下来,我们可以停止这个第一个会话,并重新启动调试器到 跳回在程序中重新运行已经执行的陈述. 这一次,我们将再次在调试器中运行for
循环的第一个迭代:
1> /Users/sammy/letter_list.py(1)<module>()
2-> def print_sammy():
3(Pdb) list
4 1 -> def print_sammy():
5 2 sammy_list = []
6 3 sammy = "sammy"
7 4 for letter in sammy:
8 5 sammy_list.append(letter)
9 6 print(sammy_list)
10 7
11 8 if __name__ == "__main__":
12 9 print_sammy()
13 10
14 11
15(Pdb) break 6
16Breakpoint 1 at /Users/sammy/letter_list.py:6
17(Pdb) continue
18> /Users/sammy/letter_list.py(6)print_sammy()
19-> print(sammy_list)
20(Pdb) pp letter
21's'
22(Pdb) jump 5
23> /Users/sammy/letter_list.py(5)print_sammy()
24-> sammy_list.append(letter)
25(Pdb) continue
26> /Users/sammy/letter_list.py(6)print_sammy()
27-> print(sammy_list)
28(Pdb) pp letter
29's'
30(Pdb) disable 1
31Disabled breakpoint 1 at /Users/sammy/letter_list.py:6
32(Pdb) continue
33['s', 's']
34['s', 's', 'a']
35['s', 's', 'a', 'm']
36['s', 's', 'a', 'm', 'm']
37['s', 's', 'a', 'm', 'm', 'y']
在上面的调试会话中,我们在第 6 行添加了休息,然后在继续后跳回第 5 行。我们沿途打印了很好的字符串,以显示字符串 s' 被附加到列表
sammy_list` 两次。
某些跳跃被调试器阻止,特别是当跳入和跳出未定义的某些流量控制陈述时.例如,在参数定义之前,您不能跳入函数,并且您不能跳入试:除
陈述的中间。
使用Python调试程序的跳跃
语句允许您在调试程序时更改执行流程,以查看流程控制是否可以用于不同的目的,或者更好地了解代码中的问题。
常见的pdb
命令表
以下是一张有用的pdb
命令的表格,以及它们的简短表格,在使用Python调试器时要记住。
命令: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表: 函数列表:
您可以从 Python 调试器文档中阅读更多有关命令和与调试器的工作。
结论
调试是任何软件开发项目的一个重要步骤,Python调试器pdb
实现了一个交互式调试环境,您可以使用任何用Python编写的程序。
借助允许您暂停程序的功能,查看变量设置的值,并以分散的方式逐步进行程序执行,您可以更全面地了解程序正在做什么,并找到存在于逻辑或解决已知问题的错误。