调试应用程序有时可能是一项不受欢迎的活动。您正忙于在时间紧迫的情况下工作,您只想让它发挥作用。然而,在其他时候,您可能正在学习新的语言功能或尝试新的方法,并希望更深入地了解某些东西是如何工作的。
无论情况如何,调试代码都是必要的,因此在调试器中舒适地工作是一个好主意。在本教程中,我将向您展示使用 pdb(Python 的交互式源代码调试器)的基础知识。
我将引导您了解 pdb 的一些常见用法。您可能需要为本教程添加书签,以便稍后在真正需要时快速参考。 pdb 和其他调试器是不可或缺的工具。当您需要调试器时,没有替代品。你真的需要它。
在本教程结束时,您将了解如何使用调试器来查看任何状态多变的在您的应用程序中。您还可以随时停止和恢复应用程序的执行流程,这样您就可以准确地看到每行代码如何影响其内部状态。
这对于追踪难以发现的错误非常有用,并允许您更快、更可靠地修复有错误的代码。有时,单步执行 pdb 中的代码并查看值如何变化可能会让人大开眼界,并带来“顿悟”时刻,以及偶尔的“捂脸”。
pdb 是 Python 标准库的一部分,因此它始终存在并且可供使用。如果您需要在无法访问您熟悉的 GUI 调试器的环境中调试代码,这可能会成为您的救星。
本教程中的示例代码使用Python 3.6。您可以在以下位置找到这些示例的源代码GitHub.
在本教程的最后,有一个快速参考基本 pdb 命令.
还有一个可打印的 pdb 命令参考,您可以在调试时用作备忘单:
入门:打印变量的值
在第一个示例中,我们将以最简单的形式使用 pdb:检查变量的值。
在要中断调试器的位置插入以下代码:
import pdb; pdb.set_trace()
当执行上面的行时,Python 会停止并等待您告诉它下一步要做什么。你会看到一个(Pdb)
迅速的。这意味着您现在已在交互式调试器中暂停并可以输入命令。
从 Python 3.7 开始,还有另一种方法可以进入调试器. 公众号 553描述内置函数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 暂停的当前源代码行。该行尚未执行。在此示例中,这是行5
在example1.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
。该行尚未执行。这是线10
在example2.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--
.
两个都n
和s
当到达当前函数末尾时将停止执行并打印--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 bpnumber
和enable 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
(下一个)。这是执行和“跳过”代码的另一种方式。和...之间的不同n
和unt
就是它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 仅在线路上停止一次10
和11
。自从unt
使用时,执行仅在循环的第一次迭代中停止。然而,循环的每次迭代都被执行。这可以在输出的最后一行中得到验证。这char
变量的值'y'
等于最后一个字符tail
的值'example4unt.py'
.
显示表达式
类似于打印表达式p
和pp
,你可以使用命令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). |
让我们看一个使用的示例u
和d
命令。在这种情况下,我们想要检查变量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
,以及局部变量fname
在get_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 命令参考,您可以在调试时将其用作备忘单:
另外,如果您想尝试基于 GUI 的 Python 调试器,请阅读我们的Python IDE 和编辑器指南看看哪些选项最适合您。快乐Python!