如何使用 Python 调试器

介绍

在软件开发中,调试是寻找并解决阻止软件正确运行的问题的过程。

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控制台,请输入quitexit命令,如果您想在程序中的任何地方明确重新启动程序,则可以使用运行命令。

使用调试器来移动一个程序

在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中的命令是nhelp命令会列出可用的名称。

突破点

您通常会使用比上面的示例更大的程序,所以您可能希望查看特定函数或行,而不是通过整个程序。

当您插入分割点时,调试器将给它分配一个数字. 分割点分配的数字是从数字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编写的程序。

借助允许您暂停程序的功能,查看变量设置的值,并以分散的方式逐步进行程序执行,您可以更全面地了解程序正在做什么,并找到存在于逻辑或解决已知问题的错误。

Published At
Categories with 技术
comments powered by Disqus