在软件开发中,调试是查找并解决阻止软件正常运行的问题的过程。
Python调试器为Python程序提供了调试环境。它支持设置条件断点、一次一行单步执行源代码、堆栈检查等。
您应该在计算机或服务器上安装 Python 3 并设置编程环境。如果您还没有设置编程环境,可以参考安装和设置指南本地编程环境或对于一个服务器上的编程环境适合您的操作系统(Ubuntu、CentOS、Debian 等)
Python 调试器作为标准 Python 发行版的一部分作为一个名为pdb
。调试器也是可扩展的,并被定义为类Pdb
。您可以阅读的官方文档pdb了解更多。
Info:要按照本教程中的示例代码进行操作,请通过运行以下命令在本地系统上打开 Python 交互式 shellpython3
命令。然后,您可以复制、粘贴或编辑示例,方法是将它们添加到>>>
prompt.
我们将从一个简短的程序开始,该程序有两个全局变量变量, a function创建一个嵌套的loop,以及if __name__ == '__main__':
建设将调用nested_loop()
功能。
循环.py
num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']
def nested_loop():
for number in num_list:
print(number)
for letter in alpha_list:
print(letter)
if __name__ == '__main__':
nested_loop()
我们现在可以使用以下命令通过 Python 调试器运行该程序:
The -m
命令行标志将为您导入任何Python模块并将其作为脚本运行。在本例中,我们导入并运行pdb
模块,我们将其传递到命令中,如上所示。
运行此命令后,您将收到以下输出:
Output
> /Users/sammy/looping.py(1)<module>()
-> num_list = [500, 600, 700]
(Pdb)
In the output, the first line contains the current module name (as indicated with <module>
) with a directory path, and the printed line number that follows (in this case it’s 1
, but if there is a comment or other non-executable line it could be a higher number). The second line shows the current line of source code that is executed here, as pdb
provides an interactive console for debugging. You can use the command help
to learn its commands, and help command
to learn more about a specific command. Note that the pdb
console is different than the Python interactive shell.
当 Python 调试器到达程序末尾时,它会自动重新启动。每当你想离开的时候pdb
控制台,输入命令quit
or exit
。如果您想在程序中的任何位置显式重新启动程序,可以使用以下命令来执行此操作run
.
在 Python 调试器中处理程序时,您可能会使用list
, step
, and next
用于移动代码的命令。我们将在本节中回顾这些命令。
在 shell 中,我们可以输入命令list
为了获得当前行周围的上下文。从程序的第一行开始looping.py
我们上面展示的——num_list = [500, 600, 700]
- 看起来像下面这样:
(Pdb) list
1 -> num_list = [500, 600, 700]
2 alpha_list = ['x', 'y', 'z']
3
4
5 def nested_loop():
6 for number in num_list:
7 print(number)
8 for letter in alpha_list:
9 print(letter)
10
11 if __name__ == '__main__':
(Pdb)
当前行用字符指示->
,在我们的例子中是程序文件的第一行。
由于这是一个相对较短的程序,因此我们几乎收到了所有程序list
命令。在不提供论据的情况下,list
命令在当前行周围提供 11 行,但您也可以指定要包含哪些行,如下所示:
(Pdb) list 3, 7
3
4
5 def nested_loop():
6 for number in num_list:
7 print(number)
(Pdb)
这里,我们要求使用命令显示第3-7行list 3, 7
.
要逐行浏览程序,我们可以使用step
or next
:
(Pdb) step
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb)
(Pdb) next
> /Users/sammy/looping.py(2)<module>()
-> alpha_list = ['x', 'y', 'z']
(Pdb)
和...之间的不同step
and next
就是它step
将在被调用函数内停止,而next
执行被调用的函数仅在当前函数的下一行停止。当我们使用该函数时,我们可以看到这种差异。
The step
一旦进入函数运行状态,命令将迭代循环,准确显示循环正在执行的操作,因为它首先会打印一个数字print(number)
然后通过打印字母print(letter)
,返回数字等:
(Pdb) step
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) step
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) step
--Call--
> /Users/sammy/looping.py(5)nested_loop()
-> def nested_loop():
(Pdb) step
> /Users/sammy/looping.py(6)nested_loop()
-> for number in num_list:
(Pdb) step
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) step
500
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
x
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb) step
> /Users/sammy/looping.py(9)nested_loop()
-> print(letter)
(Pdb) step
y
> /Users/sammy/looping.py(8)nested_loop()
-> for letter in alpha_list:
(Pdb)
The next
相反,命令将执行整个函数,而不显示分步过程。让我们退出当前会话exit
命令,然后再次启动调试器:
现在我们可以与next
命令:
(Pdb) next
> /Users/sammy/looping.py(5)<module>()
-> def nested_loop():
(Pdb) next
> /Users/sammy/looping.py(11)<module>()
-> if __name__ == '__main__':
(Pdb) next
> /Users/sammy/looping.py(12)<module>()
-> nested_loop()
(Pdb) next
500
x
y
z
600
x
y
z
700
x
y
z
--Return--
> /Users/sammy/looping.py(12)<module>()->None
-> nested_loop()
(Pdb)
在检查代码时,您可能想要检查传递给变量的值,您可以使用pp
命令,它将使用以下命令漂亮地打印表达式的值pprint module:
(Pdb) pp num_list
[500, 600, 700]
(Pdb)
大多数命令在pdb
有更短的别名。为了step
那个简短的形式是s
,并且对于next
it is n
. The help
命令将列出可用的别名。您还可以通过按ENTER
按提示键。
您通常会使用比上面的示例更大的程序,因此您可能想要查看特定的函数或行,而不是浏览整个程序。通过使用break
命令设置断点,您将运行程序直到指定的断点。
当您插入断点时,调试器会为其分配一个编号。分配给断点的数字是以数字 1 开头的连续整数,您在使用断点时可以参考该数字。
可以按照以下语法将断点放置在某些行号处<program_file>:<line_number>
如下图所示:
(Pdb) break looping.py:5
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb)
Type clear
进而y
删除所有当前断点。然后,您可以在定义函数的位置放置一个断点:
(Pdb) break looping.nested_loop
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb)
要删除当前断点,请键入clear
进而y
。您还可以设置一个条件:
(Pdb) break looping.py:7, number > 500
Breakpoint 1 at /Users/sammy/looping.py:7
(Pdb)
现在,如果我们发出continue
命令,程序将中断number
x
被评估为大于 500(即,当在外循环的第二次迭代中将其设置为等于 600 时):
(Pdb) continue
500
x
y
z
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb)
要查看当前设置为运行的断点列表,请使用以下命令break
没有任何争论。您将收到有关您设置的断点的特殊性的信息:
(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at /Users/sammy/looping.py:7
stop only if number > 500
breakpoint already hit 2 times
(Pdb)
我们还可以使用命令禁用断点disable
以及断点的数量。在此会话中,我们添加另一个断点,然后禁用第一个断点:
(Pdb) break looping.py:11
Breakpoint 2 at /Users/sammy/looping.py:11
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep no at /Users/sammy/looping.py:7
stop only if number > 500
breakpoint already hit 2 times
2 breakpoint keep yes at /Users/sammy/looping.py:11
(Pdb)
要启用断点,请使用enable
命令,要完全删除断点,请使用clear
命令:
(Pdb) enable 1
Enabled breakpoint 1 at /Users/sammy/looping.py:7
(Pdb) clear 2
Deleted breakpoint 2 at /Users/sammy/looping.py:11
(Pdb)
断点位于pdb
为您提供很多控制权。一些附加功能包括在程序当前迭代期间忽略断点ignore
命令(如ignore 1
),在断点处触发动作发生commands
命令(如command 1
),并创建临时断点,当程序第一次执行到该命令的点时,这些断点会自动清除tbreak
(例如,对于第 3 行的临时中断,您可以输入tbreak 3
).
您可以通过导入来触发调试会话pdb
模块并添加pdb
功能pdb.set_trace()
位于您希望会话开始的行上方。
在上面的示例程序中,我们将添加import
语句和我们要进入调试器的函数。对于我们的示例,让我们将其添加到嵌套循环之前。
# Import pdb module
import pdb
num_list = [500, 600, 700]
alpha_list = ['x', 'y', 'z']
def nested_loop():
for number in num_list:
print(number)
# Trigger debugger at this line
pdb.set_trace()
for letter in alpha_list:
print(letter)
if __name__ == '__main__':
nested_loop()
通过将调试器添加到代码中,您不需要以特殊方式启动程序或记住设置断点。
导入pdb
模块并运行pdb.set_trace()
函数允许您像往常一样开始程序并在其执行过程中运行调试器。
Python 调试器允许您在运行时更改程序的流程jump
命令。这可以让您向前跳以阻止某些代码运行,或者可以让您向后退以再次运行代码。
我们将使用一个小程序来创建字符串中包含的字母列表sammy = "sammy"
:
letter_list.py
def print_sammy():
sammy_list = []
sammy = "sammy"
for letter in sammy:
sammy_list.append(letter)
print(sammy_list)
if __name__ == "__main__":
print_sammy()
如果我们像往常一样运行程序python letter_list.py
命令,我们将收到以下输出:
Output
['s']
['s', 'a']
['s', 'a', 'm']
['s', 'a', 'm', 'm']
['s', 'a', 'm', 'm', 'y']
使用 Python 调试器,我们首先展示如何更改执行向前跳跃第一个周期后。当我们这样做时,我们会注意到存在中断for loop:
- python -mpdb letter_list.py
> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
1 -> def print_sammy():
2 sammy_list = []
3 sammy = "sammy"
4 for letter in sammy:
5 sammy_list.append(letter)
6 print(sammy_list)
7
8 if __name__ == "__main__":
9 print_sammy()
10
11
(Pdb) break 5
Breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) pp letter
's'
(Pdb) continue
['s']
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) jump 6
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
'a'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:5
(Pdb) continue
['s']
['s', 'm']
['s', 'm', 'm']
['s', 'm', 'm', 'y']
上面的调试会话在第 5 行放置一个中断以防止代码继续,然后继续执行代码(同时漂亮地打印一些值)letter
显示正在发生的事情)。接下来,我们使用jump
命令跳到第 6 行。此时,变量letter
设置为等于字符串'a'
,但是我们跳转将其添加到列表中的代码sammy_list
。然后我们禁用断点以像往常一样继续执行continue
命令,所以'a'
永远不会附加到sammy_list
.
接下来,我们可以退出第一个会话并重新启动调试器跳回来在程序中重新运行已经执行过的语句。这次,我们将运行第一次迭代for
在调试器中再次循环:
> /Users/sammy/letter_list.py(1)<module>()
-> def print_sammy():
(Pdb) list
1 -> def print_sammy():
2 sammy_list = []
3 sammy = "sammy"
4 for letter in sammy:
5 sammy_list.append(letter)
6 print(sammy_list)
7
8 if __name__ == "__main__":
9 print_sammy()
10
11
(Pdb) break 6
Breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) jump 5
> /Users/sammy/letter_list.py(5)print_sammy()
-> sammy_list.append(letter)
(Pdb) continue
> /Users/sammy/letter_list.py(6)print_sammy()
-> print(sammy_list)
(Pdb) pp letter
's'
(Pdb) disable 1
Disabled breakpoint 1 at /Users/sammy/letter_list.py:6
(Pdb) continue
['s', 's']
['s', 's', 'a']
['s', 's', 'a', 'm']
['s', 's', 'a', 'm', 'm']
['s', 's', 'a', 'm', 'm', 'y']
在上面的调试会话中,我们在第 6 行添加了一个中断,然后在继续后跳回第 5 行。我们一路漂亮地打印来表明字符串's'
正在被追加到列表中sammy_list
两次。然后我们禁用了第 6 行的中断并继续运行程序。输出显示两个值's'
附加到sammy_list
.
调试器会阻止某些跳转,尤其是在跳入和跳出某些未定义的流控制语句时。例如,在定义参数之前不能跳转到函数,也不能跳转到 a 的中间try:except
陈述。你也无法跳出finally
block.
The jump
使用 Python 调试器的语句允许您在调试程序时更改执行流程,以查看是否可以修改流程控制以适应不同的目的,或者更好地了解代码中出现的问题。
这是一个有用的表格pdb
使用 Python 调试器时要记住的命令及其简短形式。
Command |
Short form |
What it does |
args |
a |
Print the argument list of the current function |
break |
b |
Creates a breakpoint (requires parameters) in the program execution |
continue |
c or cont
|
Continues program execution |
help |
h |
Provides list of commands or help for a specified command |
jump |
j |
Set the next line to be executed |
list |
l |
Print the source code around the current line |
next |
n |
Continue execution until the next line in the current function is reached or returns |
step |
s |
Execute the current line, stopping at first possible occasion |
pp |
pp |
Pretty-prints the value of the expression |
quit or exit
|
q |
Aborts the program |
return |
r |
Continue execution until the current function returns |
您可以从以下位置阅读有关命令和使用调试器的更多信息Python 调试器文档.
调试是任何软件开发项目的重要步骤。 Python 调试器pdb
实现一个交互式调试环境,您可以将其与任何用 Python 编写的程序一起使用。
借助可让您暂停程序、查看变量设置的值以及以离散的逐步方式执行程序的功能,您可以更全面地了解程序正在执行的操作并查找存在的错误逻辑或解决已知问题。