Py学习  »  Python

使用pdb进行Python调试

Python程序员 • 5 年前 • 439 次点击  

Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

调试程序有时会很令人头疼。有时候是你忙于工作时间紧张,只想让它尽快成功;有时候是你正在学习一种新的语言,或者试验使用新的方法,并且希望更深入地了解某些功能是如何工作的。

无论是哪种情况,调试代码都是必需的,所以在调试器中工作是一个好主意。在本教程中,我将向你展示Python的交互式代码调试器pdb的基础知识。

我会带你了解pdb的一些常见用法。你可以将本教程添加为书签,以便稍后需要时提供参考。pdb和其他调试器是必不可少的工具。如果你需要调试器,那就没有其他可替代的了,它是必需的。

看完这篇教程,你可以知道如何使用调试器查看应用程序中任何变量的状态。你还可以随时中断和恢复程序的执行流程,以便准确查看每行代码是如何影响其内部状态的。

这对追踪难以发现的错误非常有用,并且可以让你更快更准确地修复错误代码。有时候通过pdb单步执行代码查看变量值是如何变化的,可能会让你大开眼界,让你惊呼“啊哈”,甚至感到不可思议。

pdb是Python标准库的一部分,所以它始终存在并可供使用。如果你需要在无法访问熟悉的GUI调试器的环境中调试代码,则这可以成为一种备用选择。

本教程中的示例代码使用Python 3.6。你可以在GitHub上找到这些例子的源代码。

在本教程的最后,有一个关于基本pdb命令的快速参考。

此外还有一个可打印的pdb命令参考,你在调试时可将其用作备忘手册。

入门:打印变量的值

在这第一个例子中,我们将看看使用pdb的最简单的形式:检查一个变量的值。

在想要进入调试器的位置插入以下代码:

当上面这行代码被执行时,Python会暂停执行并等待你告诉它下一步该做什么。你会看到一个(Pdb)提示。 这意味着你现在已经在交互式调试器中中断程序并可以输入命令。

从Python 3.7开始,还有另一种方法可以进入调试器。PEP 553描述了内置的函数断点breakpoint(),这使得进入调试器变得简单一致:

默认情况下,breakpoint()将导入pdb并调用pdb.set_trace(),与前述步骤一致。但是,使用breakpoint()更灵活,可以允许你通过其API调用调试行为和使用环境变量PYTHONBREAKPOINT。 例如,在环境中设置PYTHONBREAKPOINT = 0将完全禁用breakpoint(),从而禁用调试。如果你使用的是Python 3.7或更高版本,我更建议你使用breakpoint()而不是pdb.set_trace()。

你也可以通过直接在命令行运行Python并输入命令-m pdb来切入调试器,而无需修改源代码使用pdb.set_trace()或breakpoint()。如果你的程序接受命令行参数,请按常规在文件名之后传递它们。 例如:

可用的pdb命令有很多。在本教程的最后,有个基本pdb命令列表。 现在,让我们使用p命令打印变量的值。在(Pdb)提示符处输入p variable_name以打印其值。

我们来看一下这个例子。 这里是example1.py源码:

如果你在shell运行这个,你会得到以下输出:

如果你不会通过命令行运行示例或自己的代码,请参阅“如何使用Python创建自己的命令行命令?”如果你使用Windows,请查看Python Windows FAQ。

现在输入p filename。你会看到:

由于你在shell中并且使用的是CLI(命令行界面),请注意字符和格式。下面这些信息会给你你需要知道的程序暂停时的位置:

  • 由>符号开启的第一行告诉你所在的源文件。在文件名之后,括号中有当前行号。 接下来是所在函数的名称。在这个例子中,因为我们没有在函数内部或者模块级别上暂停,所以我们看到()。

  • 由- >符号开启的第二行是Python暂停的当前代码行。该行尚未执行。在这个例子中由上面的>行可知是example1.py中的第5行。

  • (Pdb)是pdb提示符。它是在等待下一个命令。

使用命令q退出调试并退出。

打印表达式

当使用print命令p时,你传递一个表达式来给Python计算。如果你传递的是一个变量名,pdb会打印它的当前值。但是,你可以做更多的事情来查看正在运行的程序的状态。

这个例子中,函数get_path()被调用。为了检查这个函数中发生了什么,我插入了一个对pdb.set_trace()的调用,以便在它返回之前中断执行:

如果你在shell中运行这个,你会看到以下输出:

同样查看暂停时位置的方法如下:

  • >:我们在源文件example2.py中的函数get_path()的第10行。这是p命令用于解析变量名称的参照系,即当前范围或前后行。

  • - >:程序的执行在return head这一行暂停。这一行尚未执行。由上面的>行可知这是example2.py中的函数get_path()的第10行。

让我们打印一些表达式来查看程序的当前状态。我最初使用命令ll(longlist)列出函数的源代码:

你可以将任何有效的Python表达式传递给p以供计算。

这对于调试工作并且希望在测试程序时直接使用实际值替代表达式时特别有用。

你也可以使用命令pp(pretty-print)来打印表达式。如果你想要打印有大量输出量的变量或表达式,例如列表和字典,则这一命令很有用。pp可以将对象保留在一行上,如果它们不在允许的宽度范围内,则会将它们分成多行。

单步执行代码

在调试时,你可以使用两个命令单步执行代码:

|  此外还有第三个命令名为unt(until)。它与n(next)有关。我们将在本教程的后续部分“继续执行”中详细描述。

n(next)和s(step)之间的区别是pdb中断的地方。

使用n(next)会继续执行,直到达到下一行并保持在当前函数内,即如果调用一个函数,则不会在函数外部中中断。将next想成“停在内部”或“跳出”。

使用s(step)执行当前行,并在调用某个外部函数时中断。将step想成“步入”。如果执行在另一个函数中中断,s将打印--Call--。

n和s都将在当前函数结束时中断执行,并在 - >一行之后打印返回值和--Return--。

我们来看看使用这两个命令的例子。这里是example3.py源码:

如果你在shell运行并输入n,你应该得到输出:

用n(next),我们停在下一行,即第15行。()说明我们停在内部,下一行则跳出函数到达了调用的get_path()。因为我们当前处于模块级别,而不是在另一个函数内暂停所以函数是()

让我们试试s:

使用s(step),我们在函数get_path()的第6行停止,因为它在第14行被调用。注意s命令后面的--Call--行。

方便的是,pdb会记住你的最后一条命令。如果你在浏览大量代码,则可以按Enter键重复上一条命令。

以下是使用s和n来遍历代码的示例。我最初输入s是因为我想“步入”函数get_path()并停止。然后我输入n来“停在内部”或“跳出”任何其他函数调用,只需按Enter键重复n命令,直到我到达最后的代码行。

注意--Call--和--Return--这两行。这是pdb让你知道执行为什么中断。n(next)和s(step)会在函数返回前停止。这就是为什么你会看到上面的--Return--行。

另请注意在第一个--Return--后面一行末尾的 - >".":

当函数返回前,pdb在函数结束时停止,它也会打印返回值。 在这个例子中返回值是"."。

列出源代码

不要忘记命令ll(longlist:列出当前函数或框架的整个源代码)。当你单步执行不熟悉的代码,或者你只是想看到整个函数的前后行时,这是非常有用的。

下面是一个例子:

要查看更短的代码片段,请使用命令l(list)。如果不带参数,它将在当前行附近打印11行或继续之前的列表。传递参数.(命令l.)会始终在当前行的周围列出11行:

使用断点

断点非常方便,可以为你节省大量时间。你不必逐步浏览数十行不感兴趣的代码,只需在要调查的地方创建一个断点即可。或者你也可以告诉pdb仅在特定条件为真时中断。

使用命令b(break)来设置断点。你可以指定执行停止的行号或函数名称。

break的语法是:

如果在行号lineno之前未指定filename: 则使用当前源文件。

注意b:condition的可选第二项参数。这非常强大。想象一下,只有在某种条件存在的情况下才中断。如果你传递一个Python表达式作为第二个参数,当表达式计算结果为真时,pdb将会中断。 我们将在下面的例子中做到这一点。

在这个例子中,有一个实用程序模块util.py。让我们设置一个断点来中断函数get_path()中的执行。

以下是主脚本example4.py的源代码:

以下是实用程序模块util.py的源代码:

首先,我们使用源文件名和行号设置一个断点:

命令c(continue)继续执行,直到下一个断点。

接下来,让我们使用函数名称设置一个断点:

输入不带任何参数的b命令以查看所有断点列表:

你可以使用disable bpnumber命令和enable bpnumber来禁用和重新启用断点。bpnumber是断点列表的第一列Num中的断点编号。注意Enb列的值更改:

删除断点使用的是命令cl(clear):

现在让我们使用Python表达式来设置断点。 想象一下,只有当你想要在函数收到某一特定输入时中断这一情况。

在此示例场景中,get_path()函数在接收到相对路径时失败,即文件路径不以/开头。在这种情况下,我将创建一个计算结果为true的表达式,并将其作为第二个参数传递给b:

如上创建断点并输入c以继续执行之后,当表达式计算结果为true时,pdb停止。命令a(args)打印当前函数的参数列表。

在上面的示例中,当你使用函数名称而不是行号设置断点时,请注意表达式应仅使用函数参数或函数输入时可用的全局变量。否则,无论表达式的值如何,断点都将停止执行。

如果需要使用具有函数内部变量的表达式,即变量名称不在函数的参数列表中,请指定行号:

你还可以使用命令tbreak设置临时断点。它首次执行后会自动移除断点。它使用与b相同的参数。

继续执行

到目前为止,我们已经研究了n(next)和s(step)的代码,并使用了b(break)和c(continue)的断点。

此外还有一个相关的命令:unt(until)。

使用unt会像c一样继续执行,但是在比当前行号更大的下一行停止。有时候unt更方便,更快速,而且正是你想要的。我将在下面的示例中演示这一点。

我们先来看看unt的语法和描述:

取决于你是否传递行号参数lineno,unt可以通过两种方式来表现:

  • 如果没有lineno,继续执行直到行号大于当前行号的行。这与n(next)类似。这是执行和“跳出”代码的替代方式。n和unt之间的区别在于,只有在到达行号大于当前行号的行时,unt才会停止。而n将停在逻辑上下一执行的行。

  • 使用lineno,继续执行直到到达行号大于当前行号的行。这就像带有行号参数c(continue)。

在这两种情况下,当前片段(函数)返回时都会停止,就像n(next)和s(step)一样。

|unt最需要注意的部分是,当行号大于或等于当前或指定行时,它将停止。

当你想要在当前源文件中继续执行并再次停下来时使用unt。你可以把它看作是n(next)和b(break)的混合体,取决于你是否传递一个行号参数。

在下面的例子中,有一个带有循环的函数。在这里,你想要继续执行代码并在循环之后停止,而不必逐步循环每次迭代或设置断点:

以下是example4unt.py的示例源代码:

以及使用unt的控制台输出:

ll命令首先用于打印函数的源代码,然后是unt。pdb会记住输入的最后一个命令,所以我只是按下Enter来重复unt命令。代码继续执行直到到达行号大于当前的行。

请注意,在上面的控制台输出中,pdb仅在第10行和第11行中断了一次。由于使用了unt,因此仅在循环的1次迭代中中断执行。 但是,循环的每次迭代都已执行完毕。这可以在最后一行输出中验证。变量char的值"y"等于"tail"的值"example4unt.py"中的最后一个字符。

未完,请看公众号中本日推送的第二篇。。。。


英文原文:https://realpython.com/python-debugging-pdb/
译者:β



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/ypwrgMQ5Kv
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/11358
 
439 次点击