如何使用Python调试器

介绍

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

Python调试器为Python程序提供了调试环境。 它支持设置条件断点,逐行扫描源代码一次一行,检查等。

与Python调试器交互工作

Python调试器作为标准Python发行版的一部分,作为一个名为pdb的模块。 调试器也是可扩展的,被定义为类Pdb 您可以阅读pdb官方文档了解更多信息。

我们将从一个简短的程序开始,该程序有两个全局变量,一个创建嵌套循环的函数,以及if __name__ == '__main__': main__ if __name__ == '__main__':将调用nested_loop()函数的结构。

looping.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调试器运行此程序:

python -m pdb looping.py

-m命令行标志将为您导入任何Python模块,并将其作为脚本运行。 在这种情况下,我们导入和运行pdb模块,我们传递到如上所示的命令。

运行此命令后,您将收到以下输出:

Output> /Users/sammy/looping.py(1)<module>()
-> num_list = [500, 600, 700]
(Pdb) 

在输出中,第一行包含具有目录路径的当前模块名称(如<module>所示),以及后面的打印行号(在这种情况下为1 ,但如果存在注释或其他不可执行行可能是更高的数字)。 第二行显示当前执行的源代码行,因为pdb提供了用于调试的交互式控制台。 您可以使用命令help来学习其命令,并help command了解有关特定命令的更多信息。 请注意, pdb控制台与Python交互式shell不同。

当Python调试器到达程序结束时,它将自动重新开始。 无论何时要离开pdb控制台,请键入命令quitexit 如果要在程序内的任何位置显式重新启动程序,可以使用命令run

使用调试器移动程序

当使用Python调试器中的程序时,您可能会使用liststepnext命令来移动代码。 本节将介绍这些命令。

在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) 

在这里,我们要求使用命令list 3, 7 3,7显示3-7行。

要逐行移动程序,我们可以使用stepnext

(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) 

stepnext之间的区别是step将在被调用的函数内停止,而next执行被调用的函数只能在当前函数的下一行停止。 当我们使用该功能时,我们可以看到这个差异。

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)

相反, next命令将执行整个功能,而不显示分步过程。 我们用exit命令退出当前会话,然后再次启动调试器:

python -m pdb looping.py

现在我们可以使用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模块漂亮地打印表达式的值:

(Pdb) pp num_list
[500, 600, 700]
(Pdb) 

pdb大多数命令具有较短的别名。 对于短格式的step ,对于nextn help命令将列出可用的别名。 您还可以在提示符下按ENTER键调用您所呼叫的最后一个命令。

断点

您通常将使用比上述示例更大的程序,因此您可能希望查看特定的函数或行,而不是整个程序。 通过使用break命令设置断点,您将运行程序直到指定的断点。

插入断点时,调试器会为其分配一个数字。 分配给断点的数字是以数字1开头的连续整数,您可以在使用断点时参考。

按照<program_file>:<line_number>的语法,可以将断点放在某些行号上,如下所示:

(Pdb) break looping.py:5
Breakpoint 1 at /Users/sammy/looping.py:5
(Pdb)

键入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命令,当x被评估为大于500时(即在外部循环的第二次迭代中被设置为等于600)时,程序将中断:

(Pdb) continue
500
x
y
z
> /Users/sammy/looping.py(7)nested_loop()
-> print(number)
(Pdb) 

要查看当前设置为运行的break列表,请使用无任何参数的命令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和断点的数字来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函数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循环中断:

python -m pdb 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行禁用了休息,并继续运行程序。 输出显示sammy_list附加的两个's'值。

调试器可以防止某些跳转,尤其是跳入和导出未定义的某些流控制语句时。 例如,在定义参数之前,您不能跳转到函数中,您不能跳转到try语句的中间try:except语句。 你也不能跳出一个finally块。

使用Python调试器的jump语句允许您在调试程序时更改执行流程,以查看流控制是否可以修改为不同的目的,或者更好地了解代码中出现的问题。

普通pdb命令表

下面是一个有用的pdb命令表以及使用Python调试器的简短形式。

命令 简写 它能做什么
args a 打印当前功能的参数列表
break b 在程序执行中创建断点(需要参数)
continue ccont 继续执行程序
help h 提供命令列表或帮助指定的命令
jump j 设置要执行的下一行
list l 打印当前行的源代码
next n 继续执行,直到达到当前功能的下一行或返回
step s 执行当前行,在第一个可能的场合停止
pp pp 漂亮打印表达式的值
quitexit q 中止程序
return r 继续执行,直到当前函数返回

您可以从Python调试器文档中阅读更多关于命令并使用调试器

结论

调试是任何软件开发项目的重要一步。 Python调试器pdb实现了一个交互式调试环境,可以与您用Python编写的任何程序一起使用。

具有让您暂停程序的功能,查看变量所设置的值,并以离散的逐步方式执行程序执行,您可以更完整地了解程序正在执行的操作,并找到存在的错误逻辑或排除已知问题。

赞(52) 打赏
未经允许不得转载:优客志 » 系统运维
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏