使用 Pdb 进行 Python 调试

2023-10-10

调试应用程序有时可能是一项不受欢迎的活动。您正忙于在时间紧迫的情况下工作,您只想让它发挥作用。然而,在其他时候,您可能正在学习新的语言功能或尝试新的方法,并希望更深入地了解某些东西是如何工作的。

无论情况如何,调试代码都是必要的,因此在调试器中舒适地工作是一个好主意。在本教程中,我将向您展示使用 pdb(Python 的交互式源代码调试器)的基础知识。

我将引导您了解 pdb 的一些常见用法。您可能需要为本教程添加书签,以便稍后在真正需要时快速参考。 pdb 和其他调试器是不可或缺的工具。当您需要调试器时,没有替代品。你真的需要它。

在本教程结束时,您将了解如何使用调试器来查看任何状态多变的在您的应用程序中。您还可以随时停止和恢复应用程序的执行流程,这样您就可以准确地看到每行代码如何影响其内部状态。

这对于追踪难以发现的错误非常有用,并允许您更快、更可靠地修复有错误的代码。有时,单步执行 pdb 中的代码并查看值如何变化可能会让人大开眼界,并带来“顿悟”时刻,以及偶尔的“捂脸”。

pdb 是 Python 标准库的一部分,因此它始终存在并且可供使用。如果您需要在无法访问您熟悉的 GUI 调试器的环境中调试代码,这可能会成为您的救星。

本教程中的示例代码使用Python 3.6。您可以在以下位置找到这些示例的源代码GitHub.

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

还有一个可打印的 pdb 命令参考,您可以在调试时用作备忘单:

免费奖金: 单击此处获取可打印的“pdb 命令参考”(PDF)您可以将其放在办公桌上并在调试时参考。

入门:打印变量的值

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

在要中断调试器的位置插入以下代码:

import pdb; pdb.set_trace()

当执行上面的行时,Python 会停止并等待您告诉它下一步要做什么。你会看到一个(Pdb)迅速的。这意味着您现在已在交互式调试器中暂停并可以输入命令。

从 Python 3.7 开始,还有另一种方法可以进入调试器. 公众号 553描述内置函数breakpoint(),这使得进入调试器变得简单且一致:

breakpoint()

默认情况下,breakpoint()将要进口 pdb并打电话pdb.set_trace(),如上图所示。然而,使用breakpoint()更加灵活,允许您通过 API 和环境变量的使用来控制调试行为PYTHONBREAKPOINT。例如,设置PYTHONBREAKPOINT=0在您的环境中将完全禁用breakpoint(),从而禁用调试。如果您使用的是 Python 3.7 或更高版本,我鼓励您使用breakpoint()代替pdb.set_trace().

您还可以闯入调试器,而无需修改源代码并使用pdb.set_trace()或者breakpoint(),通过直接从命令行运行 Python 并传递选项-m pdb。如果您的申请接受命令行参数,像平常一样在文件名后面传递它们。例如:

$ python3 -m pdb app.py arg1 arg2

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

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

#!/usr/bin/env python3

filename = __file__
import pdb; pdb.set_trace()
print(f'path = {filename}')

如果您从 shell 运行此命令,您应该得到以下输出:

$ ./example1.py 
> /code/example1.py(5)<module>()
-> print(f'path = {filename}')
(Pdb) 

如果您在从命令行获取示例或您自己的代码时遇到问题,请阅读如何使用 Python 编写自己的命令行命令?如果您使用的是 Windows,请检查Python Windows 常见问题解答.

现在输入p filename。你应该看到:

(Pdb) p filename
'./example1.py'
(Pdb)

由于您处于 shell 中并使用 CLI(命令行界面),因此请注意字符和格式。他们将为您提供所需的背景信息:

  • >从第一行开始,告诉您所在的源文件。文件名后面的括号中是当前行号。接下来是函数的名称。在这个例子中,由于我们没有在函数内部和模块级别暂停,所以我们看到<module>().
  • ->从第二行开始,是 Python 暂停的当前源代码行。该行尚未执行。在此示例中,这是行5example1.py, 来自>上面的线。
  • (Pdb)是pdb的提示符。它正在等待命令。

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

打印表达式

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

在这个例子中,函数get_path()叫做。为了检查这个函数中发生了什么,我插入了一个调用pdb.set_trace()在返回之前暂停执行:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    import pdb; pdb.set_trace()
    return head


filename = __file__
print(f'path = {get_path(filename)}')

如果您从 shell 运行此命令,您应该得到输出:

$ ./example2.py 
> /code/example2.py(10)get_path()
-> return head
(Pdb) 

我们在哪里?

  • >: 我们在源文件中example2.py在线的10在函数中get_path()。这是参考框架p命令将用于解析变量名称,即当前范围或上下文。
  • ->:执行已暂停于return head。该行尚未执行。这是线10example2.py在函数中get_path(), 来自>上面的线。

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

(Pdb) ll
  6     def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         import pdb; pdb.set_trace()
 10  ->     return head
(Pdb) p filename
'./example2.py'
(Pdb) p head, tail
('.', 'example2.py')
(Pdb) p 'filename: ' + filename
'filename: ./example2.py'
(Pdb) p get_path
<function get_path at 0x100760e18>
(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb) 

您可以将任何有效的 Python 表达式传递给p进行评估。

当您正在调试并希望在运行时直接在应用程序中测试替代实现时,这特别有用。

您还可以使用命令pp(pretty-print) 到漂亮打印表达式。如果您想打印具有大量输出的变量或表达式,例如列表和词典。如果可以的话,漂亮打印会将对象保留在单行上,如果它们不适合允许的宽度,则将它们分成多行。

单步执行代码

调试时可以使用两个命令来单步调试代码:

Command Description
n (next) Continue execution until the next line in the current function is reached or it returns.
s (step) Execute the current line and stop at the first possible occasion (either in a function that is called or in the current function).

有一个第三个命令名为unt(直到)。它与n(下一个)。我们将在本教程的稍后部分中查看它继续执行.

和...之间的不同n(下一个)和s(step) 是 pdb 停止的地方。

使用n(next) 继续执行直到下一行并停留在当前函数内,即如果调用一个外部函数,则不会在外部函数中停止。接下来可以将其视为“留在本地”或“超越”。

使用s(step) 执行当前行,并在外部函数被调用时停止。将步骤视为“步入”。如果在另一个函数中停止执行,s将打印--Call--.

两个都ns当到达当前函数末尾时将停止执行并打印--Return--以及下一行末尾的返回值->.

让我们看一个使用这两个命令的示例。这是example3.py来源:

#!/usr/bin/env python3

import os


def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    return head


filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')

如果您从 shell 运行此命令并输入n,你应该得到输出:

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) n
> /code/example3.py(15)<module>()
-> print(f'path = {filename_path}')
(Pdb) 

n(下一个),我们停线了15,下一行。我们“留在当地”<module>()并“跨过”电话get_path()。函数是<module>()因为我们目前处于模块级别,并且没有在另一个函数中暂停。

咱们试试吧s:

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) 

s(步骤),我们停在线上6在函数中get_path()因为它是在线调用的14。注意这条线--Call--之后s命令。

Conveniently, pdb remembers your last command. If you’re stepping through a lot of code, you can just press Enter to repeat the last command.

Below is an example of using both s and n to step through the code. I enter s initially because I want to “step into” the function get_path() and stop. Then I enter n once to “stay local” or “step over” any other function calls and just press Enter to repeat the n command until I get to the last source line.

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) n
> /code/example3.py(8)get_path()
-> head, tail = os.path.split(filename)
(Pdb) 
> /code/example3.py(9)get_path()
-> return head
(Pdb) 
--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb) 
> /code/example3.py(15)<module>()
-> print(f'path = {filename_path}')
(Pdb) 
path = .
--Return--
> /code/example3.py(15)<module>()->None
-> print(f'path = {filename_path}')
(Pdb) 

注意线条--Call----Return--。这是 pdb 让您知道执行停止的原因。n(下一个)和s(step) 将在函数返回之前停止。这就是为什么你会看到--Return--上面的行。

另请注意->'.'在第一个之后的行尾--Return--多于:

--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb) 

当 pdb 在返回之前停止在函数末尾时,它还会为您打印返回值。在这个例子中是'.'.

列出源代码

不要忘记命令ll(长列表:列出当前函数或框架的完整源代码)。当您单步执行不熟悉的代码或者您只想查看整个函数的上下文时,这确实很有帮助。

这是一个例子:

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
  6  -> def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         return head
(Pdb) 

要查看较短的代码片段,请使用命令l(列表)。如果没有参数,它将在当前行周围打印 11 行或继续前面的列表。传递论点.始终列出当前行周围的 11 行:l .

$ ./example3.py 
> /code/example3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) l
  9         return head
 10     
 11     
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
  9         return head
 10     
 11     
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) 

使用断点

断点非常方便,可以节省你很多时间。无需单步执行数十行您不感兴趣的代码,只需在您想要调查的位置创建一个断点即可。或者,您还可以告诉 pdb 仅在特定条件为真时中断。

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

中断的语法是:

b(reak) [ ([filename:]lineno | function) [, condition] ]

If filename:行号之前没有指定lineno,然后使用当前源文件。

请注意可选的第二个参数b: condition。这是非常强大的。想象一下这样一种情况,只有当某种条件存在时你才想打破。如果传递 Python 表达式作为第二个参数,则当表达式计算结果为 true 时,pdb 将中断。我们将在下面的示例中执行此操作。

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

这是主脚本的来源example4.py:

#!/usr/bin/env python3

import util

filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')

这是实用程序模块的来源util.py:

def get_path(filename):
    """Return file's path or empty string if no path."""
    import os
    head, tail = os.path.split(filename)
    return head

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

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb) 

命令c(继续)继续执行,直到找到断点。

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

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./example4.py'
(Pdb) 

进入b不带参数查看所有断点的列表:

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb) 

您可以使用以下命令禁用和重新启用断点disable bpnumberenable bpnumber. bpnumber是断点列表第一列中的断点编号Num。注意Enb列的值变化:

(Pdb) disable 1
Disabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb) 

要删除断点,请使用命令cl(清除):

cl(ear) filename:lineno
cl(ear) [bpnumber [bpnumber...]]

现在让我们使用 Python 表达式来设置断点。想象一下这样一种情况,只有当有问题的函数收到特定输入时,您才想中断。

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

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path, not filename.startswith('/')
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) a
filename = './example4.py'
(Pdb) 

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

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

如果您需要使用位于函数内部的变量名的表达式进行中断,即变量名不在函数的参数列表中,请指定行号:

$ ./example4.py 
> /code/example4.py(7)<module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5, not head.startswith('/')
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p head
'.'
(Pdb) a
filename = './example4.py'
(Pdb) 

您还可以使用命令设置临时断点tbreak。第一次点击时它会自动删除。它使用与以下相同的参数b.

继续执行

到目前为止,我们已经研究了单步执行代码n(下一个)和s(步骤)并使用断点b(休息)和c(继续)。

还有一个相关的命令:unt(直到)。

使用unt继续执行像c,但停在比当前行大的下一行。有时unt使用起来更方便快捷,正是您想要的。我将通过下面的示例来演示这一点。

我们首先看一下语法和描述unt:

Command Syntax Description
unt unt(il) [lineno] Without lineno, continue execution until the line with a number greater than the current one is reached. With lineno, continue execution until a line with a number greater or equal to that is reached. In both cases, also stop when the current frame returns.

取决于您是否传递行号参数lineno, unt可以有两种表现方式:

  • 没有lineno,继续执行,直到到达编号大于当前编号的行。这类似于n(下一个)。这是执行和“跳过”代码的另一种方式。和...之间的不同nunt就是它unt仅当到达编号大于当前编号的行时才停止。n将停止在下一个逻辑执行行。
  • lineno,继续执行,直到到达数字大于或等于该数字的行。这就像c(继续)带有行号参数。

在这两种情况下,unt当前帧(函数)返回时停止,就像n(下一个)和s(步)。

需要注意的主要行为unt是当行号达到时就会停止大于或等于到当前或指定行。

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

在下面的示例中,有一个带有循环的函数。在这里,您希望继续执行代码并在循环后停止,而不单步执行循环的每次迭代或设置断点:

这是示例源example4unt.py:

#!/usr/bin/env python3

import os


def get_path(fname):
    """Return file's path or empty string if no path."""
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    for char in tail:
        pass  # Check filename char
    return head


filename = __file__
filename_path = get_path(filename)
print(f'path = {filename_path}')

控制台输出使用unt:

$ ./example4unt.py 
> /code/example4unt.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) unt
> /code/example4unt.py(10)get_path()
-> for char in tail:
(Pdb) 
> /code/example4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb) 
> /code/example4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'example4unt.py')

The ll command was used first to print the function’s source, followed by unt. pdb remembers the last command entered, so I just pressed Enter to repeat the unt command. This continued execution through the code until a source line greater than the current line was reached.

请注意,在上面的控制台输出中,pdb 仅在线路上停止一次1011。自从unt使用时,执行仅在循环的第一次迭代中停止。然而,循环的每次迭代都被执行。这可以在输出的最后一行中得到验证。这char变量的值'y'等于最后一个字符tail的值'example4unt.py'.

显示表达式

类似于打印表达式ppp,你可以使用命令display [expression]告诉 pdb 在执行停止时自动显示表达式的值(如果它发生了变化)。使用命令undisplay [expression]清除显示表达式。

以下是这两个命令的语法和说明:

Command Syntax Description
display display [expression] Display the value of expression if it changed, each time execution stops in the current frame. Without expression, list all display expressions for the current frame.
undisplay undisplay [expression] Do not display expression any more in the current frame. Without expression, clear all display expressions for the current frame.

下面是一个例子,example4display.py,通过循环演示其用法:

$ ./example4display.py 
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) 
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb) 
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

在上面的输出中,pdb 自动显示了char变量,因为每次命中断点时它的值都会改变。有时这很有帮助并且正是您想要的,但是还有另一种使用方法display.

您可以输入display多次构建表达式观察列表。这比p。添加您感兴趣的所有表达式后,只需输入display查看当前值:

$ ./example4display.py 
> /code/example4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/example4display.py:11
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './example4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'example4display.py'
(Pdb) c
> /code/example4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './example4display.py'
head: '.'
tail: 'example4display.py'

Python 来电显示

在最后一节中,我们将在迄今为止所学的基础上进行学习,并获得丰厚的回报。我使用“来电显示”这个名称来指代电话系统的来电显示功能。这正是本示例所演示的内容,只不过它适用于 Python。

这是主脚本的来源example5.py:

#!/usr/bin/env python3

import fileutil


def get_file_info(full_fname):
    file_path = fileutil.get_path(full_fname)
    return file_path


filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')

这是实用程序模块fileutil.py:

def get_path(fname):
    """Return file's path or empty string if no path."""
    import os
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    return head

在这种情况下,假设有一个大型代码库,其中的实用程序模块中有一个函数,get_path(),这是用无效输入调用的。然而,它是从不同的包中的许多地方调用的。

如何找到来电者是谁?

使用命令w(其中)打印堆栈跟踪,最新的帧位于底部:

$ ./example5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) 

如果这看起来很混乱或者您不确定堆栈跟踪或帧是什么,请不要担心。我将在下面解释这些术语。这并不像听起来那么困难。

由于最新的帧位于底部,因此从那里开始并从下向上阅读。查看以以下内容开头的行->,但跳过第一个实例,因为那是pdb.set_trace()用于在函数中输入pdbget_path()。在此示例中,调用该函数的源代码行get_path()是:

-> file_path = fileutil.get_path(full_fname)

每个上面的行->包含文件名、行号(在括号中)和源代码行所在的函数名称。因此调用者是:

  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

在这个用于演示目的的小示例中,这并不奇怪,但想象一下在一个大型应用程序中,您设置了一个断点,并带有一个条件来识别错误输入值的来源。

现在我们知道如何找到呼叫者了。

但是堆栈跟踪和帧的东西呢?

A 堆栈跟踪只是 Python 为跟踪函数调用而创建的所有框架的列表。框架是 Python 在调用函数时创建并在函数返回时删除的数据结构。堆栈只是任意时间点的帧或函数调用的有序列表。随着函数被调用然后返回,(函数调用)堆栈在应用程序的整个生命周期中不断增长和收缩。

打印时,这个有序的帧列表(堆栈)称为堆栈跟踪。随时输入命令就可以看到w,就像我们上面所做的那样找到调用者。

看到这个维基百科上的调用堆栈文章了解详情。

为了更好地理解并从 pdb 中获得更多信息,让我们更仔细地查看以下帮助:w:

(Pdb) h w
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands. 'bt' is an alias for this command.

pdb中的“当前帧”是什么意思?

将当前帧视为 pdb 已停止执行的当前函数。换句话说,当前帧是应用程序当前暂停的位置,并用作 pdb 命令的参考“帧”,例如p(打印)。

p和其他命令将在需要时使用当前帧作为上下文。如果是p,当前帧将用于查找和打印变量引用。

当 pdb 打印堆栈跟踪时,会出现一个箭头>表示当前帧。

这有什么用?

可以使用这两个命令u(上)和d(向下)更改当前帧。结合p,这允许您在任何帧中沿调用堆栈的任何点检查应用程序中的变量和状态。

以下是这两个命令的语法和说明:

Command Syntax Description
u u(p) [count] Move the current frame count (default one) levels up in the stack trace (to an older frame).
d d(own) [count] Move the current frame count (default one) levels down in the stack trace (to a newer frame).

让我们看一个使用的示例ud命令。在这种情况下,我们想要检查变量full_fname这是该函数的本地函数get_file_info()example5.py。为了做到这一点,我们必须使用命令将当前帧向上一级更改u:

$ ./example5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
  /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
(Pdb) p full_fname
'./example5.py'
(Pdb) d
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) p fname
'./example5.py'
(Pdb) 

致电给pdb.set_trace()是在fileutil.py在函数中get_path(),因此当前帧最初设置在那里。您可以在上面的第一行输出中看到它:

> /code/fileutil.py(5)get_path()

访问并打印局部变量full_fname在函数中get_file_info()example5.py, 命令u用于提升一级:

(Pdb) u
> /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

请注意输出中u在 pdb 上面打印了箭头>在第一行的开头。这是 pdb,让您知道帧已更改,并且此源位置现在是当前帧。变量full_fname现在可以访问了。另外,了解以以下内容开头的源代码行也很重要:->第 2 行已执行。由于框架已在堆栈中向上移动,fileutil.get_path()已被调用。使用u,我们将堆栈向上移动(在某种意义上,回到过去)到函数example5.get_file_info()在哪里fileutil.get_path()被称为。

继续这个例子,之后full_fname被打印后,当前帧被移动到其原始位置d,以及局部变量fnameget_path()被打印了。

如果我们愿意,我们可以通过传递count论证u或者d。例如,我们可以转移到模块级别example5.py通过输入u 2:

$ ./example5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u 2
> /code/example5.py(12)<module>()
-> filename_path = get_file_info(filename)
(Pdb) p filename
'./example5.py'
(Pdb) 

当您调试和思考许多不同的事情时,很容易忘记自己在哪里。请记住,您始终可以使用适当命名的命令w(where) 查看执行暂停的位置以及当前帧是什么。

基本 pdb 命令

一旦您花了一点时间使用 pdb,您就会意识到一点知识大有帮助。始终可以通过以下方式获得帮助h命令。

只需输入h或者help <topic>获取所有命令的列表或特定命令或主题的帮助。

为了快速参考,以下是基本命令的列表:

Command Description
p Print the value of an expression.
pp Pretty-print the value of an expression.
n Continue execution until the next line in the current function is reached or it returns.
s Execute the current line and stop at the first possible occasion (either in a function that is called or in the current function).
c Continue execution and only stop when a breakpoint is encountered.
unt Continue execution until the line with a number greater than the current one is reached. With a line number argument, continue execution until a line with a number greater or equal to that is reached.
l List source code for the current file. Without arguments, list 11 lines around the current line or continue the previous listing.
ll List the whole source code for the current function or frame.
b With no arguments, list all breaks. With a line number argument, set a breakpoint at this line in the current file.
w Print a stack trace, with the most recent frame at the bottom. An arrow indicates the current frame, which determines the context of most commands.
u Move the current frame count (default one) levels up in the stack trace (to an older frame).
d Move the current frame count (default one) levels down in the stack trace (to a newer frame).
h See a list of available commands.
h <topic> Show help for a command or topic.
h pdb Show the full pdb documentation.
q Quit the debugger and exit.

使用 pdb 进行 Python 调试:结论

在本教程中,我们介绍了 pdb 的一些基本和常见用法:

  • 打印表达式
  • 单步执行代码n(下一个)和s(步)
  • 使用断点
  • 继续执行unt(直到)
  • 显示表达式
  • 查找函数的调用者

我希望这对你有帮助。如果您想了解更多信息,请参阅:

  • 您附近的 pdb 提示符处有 pdb 的完整文档:(Pdb) h pdb
  • Python 的 pdb 文档

示例中使用的源代码可以在相关的GitHub 存储库。请务必查看我们的可打印 pdb 命令参考,您可以在调试时将其用作备忘单:

免费奖金: 单击此处获取可打印的“pdb 命令参考”(PDF)您可以将其放在办公桌上并在调试时参考。

另外,如果您想尝试基于 GUI 的 Python 调试器,请阅读我们的Python IDE 和编辑器指南看看哪些选项最适合您。快乐Python!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Pdb 进行 Python 调试 的相关文章

  • 使用 ESP-Prog / Jlink 进行 JTAG 调试时的常见错误及解决办法

    此篇博客用来记录使用 ESP PROG Jlink 来对 ESP32 Lyrat 进行 JTAG 调试时遇到的一些问题以及解决办法 如果对进行 JTAG 相关操作有疑惑 请参考以下资料 使用 ESP Prog 进行 JTAG 调试 使用 J
  • 69个网盘搜索引擎资源(最全)

    呵呵 今天博主今天整理了一个下午 把网上的能找到69个网盘搜索引擎都放在这了 希望能帮到有需要的小伙伴 1 盘多多 http www panduoduo net 2 Bdsola http www 3134 c 3 探索云盘搜索 http
  • Debug-CDK编译

    问题描述 make No rule to make target xxx c needed by Obj xxx o Stop 解决方法 删掉obj文件夹 重新编译
  • oracle 释放过度使用的Undo表空间

    故障现象 UNDO表空间越来越大 长此下去最终数据因为磁盘空间不足而崩溃 问题分析 产生问题的原因主要以下两点 1 有较大的事务量让Oracle Undo自动扩展 产生过度占用磁盘空间的情况 2 有较大事务没有收缩或者没有提交所导制 说 明
  • python依赖管理工具

    python依赖管理方案 pip作为python默认的包管理工具 提供了在线安装python依赖包的工具 但是内置的pip freeze默认打包整个机器上的python依赖环境 不太友好 下面简单介绍4个常用的依赖管理工具 pip pip
  • 最好用的五个黑科技搜索引擎推荐

    一 数据搜 http data chongbuluo com 数据搜 这个网站就是搜索一些热词和数据指数的 包括百度指数 阿里指数 微博指数 微信指数 搜狗指数等等 当然 还有一些汽车数据 腾讯大数据 票房数据相关数据查询网站 估计很多人经
  • qtp的基本使用方法(1)

    1 action qtp为每一个action生成相应的测试文件和目录 对象库也是和action绑定的 用action 来划分和组织测试流程 编辑action 修改action的名字 action properties 增加action in
  • 如何创建Silverlight 项目

    Silverlight Silverlight Tools 您可以使用已经掌握的技术和熟悉的工具创建基于 Silverlight 的应用程序 本主题介绍开发用于 Silverlight 3 的应用程序时可使用的各种工具 本主题包括下列各节
  • 更改cpuID(CPU序列号)指南

    最近在使用一个软件 购买了许可 该license绑定了机器的cpuID 然该软件并不能正在运行在windows 8 64bit操作系统上 具体表现为运行3 5mins后就崩溃 于是 我计划使用VMware虚拟机虚拟出相应的硬件以及windo
  • git clone 下载 其他分支

    总是记不住 可能是因为用得少 如果 已经 clone了 master分支 方法 1 那么 本地 git pull 然后执行 git checkout b 本地分支名 origin 远程分支名 这样就能下载 到远程分支 并建立本地关联 方法2
  • VS code-设置问题

    起因 VS code本来是默认支持禁用非活动区域代码着色的 但我不知道怎么点取消了 今天又设置回来 因为感觉这样看代码方便点 方法 如果设置项旁边还有灰色小字提示与工作区不同 需要点一下将工作区设置也勾上 我就是只设置了用户区 没设置工作区
  • WINDOWS窗体应用程序与WPF应用程序的区别?

    WINDOWS窗体应用程序与WPF应用程序的区别 用WPF更容易做的漂亮 感觉有以下几个大区别 WPF的线程模型和winform的线程模型不同 WPF到处用到数据绑定 winform在列表中才用的多 WPF支持3D winform本身没有
  • 【yolov7系列二】正负样本分配策略

    本文主要就yolov7的正负样本筛选策略 并与yolov5 yolov6进行比对 首先接着上一篇yolov7系列一 网络整体结构 填几个小坑 希望对大家没有造成困扰 如 E ELAN层 在cat后需要要conv层做特征融合 还有SPPCSP
  • 开发工具之 Snipaste(超级截图工具)

    snipaste工具是一款开源免费的超级截图工具 这里小编强烈推荐此工具的使用 前言 当你使用ALT TAB习惯性的来回切屏的时候 其实在这个过程中 仔细想想是不是比较累 这样子做久了很容易导致疲劳 所以小编强推贴图功能 好了废话不多说 直
  • HTML对字体的操作详解

    摘自 HTML对字体的所有操作详解 经典 作者 HeroKern 发布时间 2016 01 31 21 15 31 网址 https blog csdn net qq 21792169 article details 50615919 ut
  • 什么是思维导图?6 个开源免费的思维导图软件

    目录 15款思维导图工具推荐 什么是思维导图 6 个开源免费的思维导图软件 当前推荐 Freeplane 离线应用 有免安装版本 跨平台 目前 2023年 还在更新中 下载 https sourceforge net projects fr
  • idea git提交忽略文件

    1 在idea的Plugin中 搜索 ignore插件 并安装使用 安装成功后会重启idea 2 在项目的根目录下创建一个创建一个 gitignore file 3 编辑gitignore文件 4 提交gitignore文件后 在该文件中限
  • PyQt 布局:创建具有专业外观的 GUI 应用程序

    目录 在 GUI 上布置图形元素 储备 PyQt 布局库 Using General Purpose Layout Managers 构建水平布局 QHBoxLayout 构建垂直布局 QVBoxLayout 在网格中排列小部件 QGrid
  • Python 中的 Minimax:学习如何输掉 Nim 游戏

    目录 玩一个简化的 Nim 游戏 Get to Know the Minimax Algorithm 探索游戏树 找到最佳的下一步行动 Lose the Game of Nim Against a Python Minimax Player
  • 使用 Python 发送电子邮件

    目录 Getting Started 选项 1 设置 Gmail 帐户进行开发 选项 2 设置本地 SMTP 服务器 Sending a Plain Text Email 启动安全 SMTP 连接 发送您的纯文本电子邮件 Sending F

随机推荐

  • PyCon Africa 2019(回顾)

    目录 PyCon Africa 发生了什么 主会议 穆斯塔法 西塞 人工智能产生积极影响的潜力 Meili Triantafyllidi 在柏林 PyLadies 工作 6 年的经验教训 Candy Tricia Khohliwe 网络虚拟
  • 第 142 集:使用 Apache Airflow 协调大型和小型项目

    第 142 集 使用 Apache Airflow 协调大型和小型项目 真正的 Python 播客 2023 年 1 月 27 日54m RSS Apple Podcasts Google Podcasts Spotify More 播客瘾
  • Python 新闻:2023 年 3 月以来的新增内容

    目录 Python 3 12 0 Alpha 6 发布 Python 本地包目录上的 PEP 582 被拒绝 PyCascades 2023 在不列颠哥伦比亚省温哥华举行 PyCon US 2023 招募志愿者 PyPI 发布博客 2022
  • 使用Python或运算符

    Python 中有 3 个布尔运算符 and or 和not 使用它们 您可以测试条件并决定程序将采用哪个执行路径 在本教程中 您将深入了解 Pythonor运算符及其使用方法 在本课程结束时 您将学到 Python 如何or操作员工作 如
  • Python F 字符串:讨厌的细节

    您已经了解了很多有关 F 弦的知识以及它们为何如此出色 在本课程中 您将了解在使用以下内部 f 字符串时要记住哪些细节 引号 词典 牙套 反斜杠
  • Python mmap:使用内存映射进行文件 I/O

    这Python之禅有很多智慧可以提供 一个特别有用的想法是 应该有一种 最好只有一种 明显的方法来做到这一点 然而 在 Python 中完成大多数事情有多种方法 而且通常都有充分的理由 例如 有Python 读取文件的多种方法 包括很少使用
  • Python 图形用户界面编程

    Python 图形用户界面编程 学习路径 技能 图形用户界面 GUI Python支持多种GUI框架或工具包 从传统上与 Python 捆绑在一起的 Tkinter 到许多跨平台解决方案 例如 PyQT 或 wxPython 您可以将其作为
  • 第 78 集:通过图解故事学习 Python

    第 78 集 通过图解故事学习 Python 真正的 Python 播客 2021 年 9 月 17 日48m RSS Apple Podcasts Google Podcasts Spotify More 播客瘾君子 灰蒙蒙 袖珍铸件 投
  • Django 测试(第 1 部分)——最佳实践和示例

    目录 Intro to Testing in Django 测试类型 最佳实践 结构 第三方包 Examples 设置 测试模型 测试视图 测试表格 测试 API 下次 测试至关重要 如果没有正确测试您的代码 您将永远不会知道代码现在或将来
  • Python 3.11 预览:TOML 和 tomllib

    目录 Python 3 11 Beta 很酷的新功能 安装 tomllib TOML Parser in Python 3 11 学习基本 TOML 使用 tomllib 读取 TOML 写入TOML Other New Features
  • Python 包版本控制最佳实践

    如果您添加新功能或添加重大更改 您需要更改软件包的版本 在本课程中 您将了解语义版本控制以及如何在项目中使用它 笔记 如果您想了解有关语义版本控制的更多信息 请务必查看semver org
  • 2021 年 7 月 21 日

    本周我们有一位特别嘉宾联合主持人 Real Python 自己的巴托什 扎钦斯基 David 和 Bartosz 联手回答会员的问题 在这次会议上 我们讨论了 Python 新闻和更新 什么是 REST API 有哪些学习它们的好资源 什么
  • 字符串索引

    弦乐是字符数据的有序序列 索引允许您使用数值直接访问字符串中的各个字符 字符串索引是零基础 字符串中第一个字符的索引为 0 下一个字符为 1 依此类推 在本课程中 您将学习字符串索引语法并通过几个示例进行练习 gt gt gt gt gt
  • 更精确的类型

    在本课中 您将了解Python 3 8 中更精确的类型 Python 的类型系统目前已经相当成熟 然而 在 Python 3 8 中 输入中添加了一些新功能 以允许更精确的输入 文字类型 类型化词典 最终对象 协议 Python支持可选类型
  • 第 145 集:使用 Nox 创建 Python Wordle 克隆并测试环境

    第 145 集 使用 Nox 创建 Python Wordle 克隆并测试环境 真正的 Python 播客 2023 年 2 月 17 日59m RSS Apple Podcasts Google Podcasts Spotify More
  • 鳞片层

    有关本课程所涵盖概念的更多信息 您可以查看以下资源 图九 数据 经济学 绘图文件 秤 API 参考 绘图文件
  • Python 社区采访 Will McGugan

    今天我加入了威尔 麦古根 Will 是一位来自苏格兰的自由 Python 开发人员 也是流行 Python 库的创建者富有的 在这次采访中 我们讨论了Rich 维护一个流行的 Python 包 构建国际象棋引擎以及 Will 对摄影的热爱是
  • 从数据到可视化

    使用 Bokeh 构建可视化涉及以下步骤 准备数据 确定可视化的呈现位置 设置图形 连接并绘制您的数据 整理布局 预览并保存您精美的数据创建 该视频将更详细地探讨每个步骤 下面的模板可用于探索本课程中使用的六个构建块 Bokeh Visua
  • 如何在 Python 编程面试中脱颖而出

    目录 Select the Right Built In Function for the Job 使用 enumerate 而不是 range 进行迭代 使用列表推导式代替map 和filter 使用breakpoint 而不是print
  • 使用 Pdb 进行 Python 调试

    目录 入门 打印变量的值 打印表达式 Stepping Through Code 列出源代码 使用断点 继续执行 显示表达式 Python 来电显示 基本 pdb 命令 使用 pdb 进行 Python 调试 结论 调试应用程序有时可能是一