如何使用pdb进行Python调试

调试应用有时是一个不受欢迎的工作,当你长期编码之后,只希望写的代码顺利运行。但是,很多情况下,我们需要学习一个新的语言功能或者实验检测新的方法,从而去理解其中运行的机制原理。

即使不考虑这样的场景,调试代码仍然是有必要的,所以学会在工作中使用调试器是很重要的。本篇教程中,我将会给出基本的使用关于pdb----Python‘s interative source code debugger。

首先给出一些pdb的基础知识,大家可以保存这篇文章方便后续的阅读。pdb类似于其他的调试器,是独立的工具,当你需要一个调试器时,它们是不可替代的。本篇教程的最后,大家将会学习到如何使用调试器来查看应用中的任何变量,可以在任何时刻停止或恢复应用执行流程,从而了解到每行代码是如何影响应用的内部状态的。

这有助于追踪难以发现的bug,并且实现快速可靠的解决缺陷代码。有时候,在pdb中单步调试代码,然后查看变量值的变化,可以有助于我们对应用代码的深层次理解。pdb是Python标准库的一部分,所以只要我们使用了Python解释器,也就可以使用pdb,非常方便。

本文所使用的实例代码会放在文章的末尾,使用的环境是Python3.6及以后的解释器,大家可以下载源码方便学习。

1.开始阶段:答应一个变量值

开始案例,我们首先探索pdb最简单的使用:查阅一个变量的值。首先,我们在一个源码文件中某一行,写入下列语句:

import pdb; pdb.set_trace()

当这一行代码被执行后,Python代码文件就会被暂停运行,等待你发布命令来指导它下一步如何操作。运行上面的代码后,你可以在命令行界面看到(Pdb)的提示符,这就意味着,代码被停止了,等待命令的输入。

自从Python3.7以来,官方标准建议使用标准库内部函数breakpoint()替代上面的代码(注意:本文附带的代码都使用的上面代码形式),这样可以更加加快解释器的运行和调试:

breakpoint()

默认情况下,breakpoint()将会倒入pdb模块,然后调用pdb.set_trace()函数,只是它进行了进一步封装。但是,使用breakpoint()可以更加灵活,并且允许用户通过调用它的API来控制调试行为,以及使用环境变量PYTHONBREAKPOINT。例如,当我们设置PYTHONBREAKPOINT=0在我们的环境中,这就会完全关闭breakpoint()的功能,从而关闭调试功能。

此外,我们还可以不用手动在源代码文件中加入断点代码,只需要在命令行输入运行指令时通过参数传递来设置,例如:

$ python3 -m pdb app.py arg1 arg2

那么,让我们直接进入本小节的内容,也就是查阅代码中变量的值,看下面的案例,使用的源码文件是codeExample1.py:

#!/usr/bin/env python3

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

而在你命令行界面,运行上述的Python代码,你就可以得到下面的输出结果:

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

接下来,让我们输入p filename这个命令,来查看filename这个变量的值,可以看到下述结果:

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

因为我们使用的是一个命令行接口界面程序(command-line interface),那么注意一下输出的字符和格式,解释如下:

  • >开始的第一行告诉我们所运行的源码文件名称,源码名称之后,用小括号包含的是当前代码行数,再后面就是函数名称。这里,因为我们没用调用任何函数,而是处于模块级别,所用见到的是()。
  • ->开始的第二行表示目前行数代码对应的具体代码内容。
  • (Pdb)是一个pdb提示符,等待下一个命令的输入。

我们可以使用q命令,表示推出调试(quit)。

2.打印表达式

当使用命令p,我们同样可以输入一个表达式,让Python来计算表达式的值。 如果传入一个变量名,pdb就会答应当前变量对应的值。但是,我们可以进一步调查我们应用程序当前的运行状态。

下面案例中,当函数get_path()被调用,为了查看在这个函数中发生了什么,我已经提前插入断点程序pdb.set_trace(),来阻断程序的运行,源码codeExample2.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)
    import pdb; pdb.set_trace()
    return head

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

如果在命令行中运行这个文件,可以得到下面的输出:

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

那么此时,我们处于什么阶段:

  • >:表示我们在源码文件codeExample2.py的第10行代码处,此处是函数get_path()。如果运行命令p输出的标量,就是当前所引用的代码帧,也就是当前上下文内的标量。
  • ->:运行的代码已经被停止在return head处,这一行代码还没有被执行,是位于codeExample2.py文件的第10行处,具体是在函数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
'./codeExample2.py'
(Pdb) p head, tail
('.', 'codeExample2.py')
(Pdb) p 'filename: ' + filename
'filename: ./codeExample2.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(漂亮打印)来美观打印表达式。 如果您想打印具有大量输出的变量或表达式,例如列表和字典。 如果可以,漂亮打印将对象保留在一行上,如果它们不适合允许的宽度,则将它们分成多行。

3.调试代码

在这一部分,我们主要是用两个命令来实现代码的调试,如下图所示:

命令n(next)和s(step)的区别是,pdb运行停止的位置。使用n(next),pdb会进行执行,直到运行到当前函数或模块的下一行代码,即:如果有外部函数被调用,不会跳转到外部函数代码中,可以把n理解为“step over”。使用s(step)来执行当前代码,但是如果有外部函数被调用,会跳转到外部函数中,可以理解为“step into”,如果执行到外部跳转函数,s命令会输出--Call--。

n(next)和s(step)命令都会暂定代码的执行,当运行到当前函数的结束部分,并且打印出--Return--,下面是codeExample3.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}')

如果在命令行中运行这个文件,同时输入命令n,可以得到下面的输出:

$ ./codeExample3.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(next),我们停止在15行代码,并且我们也只是在此模块中,没有跳转到函数get_path()。此处函数表示为(),表明我们目前是处于模块级别而不是任何函数内部。

再让我们尝试使用s(step)命令,输出为:

$ ./codeExample3.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(step)命令,我们停止在函数get_path()内部的第6行代码处,因为这个函数是在代码文件中第14行处被调用的,注意在s命令之后输出了--Call--,表明是函数调用。为了方便,pdb有命令记忆功能,如果我们要调试很多代码,可以输入Enter回车键,来重复执行命令。

下面是我们混合使用n(next)和s(step)命令的案例,首先输入s(step),因为我们想要进入函数get_path(),然后通过命令n(next)在局部调试代码,并且使用Enter回车键,避免重复输入命令:

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

注意输出的--Call--和--Return--,这是pdb输出的信息,提示我们调试过程的状态信息,n(next)和s(step)命令都会在函数返回后停止,这也就是我们会看到--Return--信息的输出。此外,还要注意->'.'在第一个--Return--输出上面的结尾处:

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

当pdb停止到一个函数的结尾,但是还没有运行到return时,pdb同样会打印return值,在上面例子中就是'.'.

3.1显示代码

不要忘记,我们上面提到了命令ll(longlist:显示当前函数或帧的源代码),在我们调试进入到不熟悉的代码上下文中,这个命令非常有效,我们可以打印出整个函数代码,显示样例如下:

$ ./codeExample3.py
> /code/codeExample3.py(14)<module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/codeExample3.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(list),不需要输入参数,它将会打印11行目前代码附近的代码内容,案例如下:

$ ./codeExample3.py
> /code/codeExample3.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) 

4.断点使用

断点的正确使用,在我们调试过程中可以节约大量的时间。不需要单步调试一行行代码,而只用简单地在我们想要查阅的地方设置一个断点,就可以直接运行到断点处进行调试。同样的,我们可以添加条件,来让pdb判断是否需要设置断点。通常使用命令b(break)来设置一个断点,我们可以通过指定代码行数,或者是想要调试的函数名称,语法如下:

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

如果filename:没有在代码行数之前被指定,那么默认就是当前代码文件中。注意第二个可选参数是b: condition,这个功能非常强大。假设在一个场景下,我们想要在某种条件成立的情况下设置断点,如果我们传入一个表达式座位第二个参数,pdb会在计算改表达式为true的情况下设置断点,我们会在下面给出案例。下面案例中,使用了一个工具模块util.py,让我们在函数get_path()中设置一个断点,下面是代码codeExample4.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(continue)是实现被断点停止后继续运行的命令,下面,让我们使用函数名来设置断点:

$ ./codeExample4.py
> /code/codeExample4.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
'./codeExample4.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(clear):

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

现在,让我们尝试在设置断点时输入表达式参数,在当前案例场景下,get_path()函数如果接受到一个相对路径,即:如果路径名称不是以/开始的,那么就不会设置断点,具体案例如下:

$ ./codeExample4.py
> /code/codeExample4.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 = './codeExample4.py'
(Pdb) 

如果创建一个断点后,继续输入c(continue)命令来运行,pdb只会在表达式计算为true的情况下停止运行。命令a(args)会打印出当前函数的传入参数。

上述案例中,如果你通过函数名称而不是代码行数来设置断点,注意表达式只会使用当前函数的参数或者全局变量,不然的话,断点就会不去计算表达式,而直接停止函数运行。如果,我们还是想用不是当前的函数变量来计算表达式,也就是使用的变量不在当前函数参数列表中,那么就要指定代码行数,案例如下:

$ ./codeExample4.py
> /code/codeExample4.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 = './codeExample4.py'
(Pdb) 

5.继续执行代码

目前,我们可以使用n(next)和s(step)命令来调试查看代码,然后使用命令b(break)和c(continue)来停止或继续代码的运行,这里还有一个相关的命令:unt(until)。使用unt命令类似于c命令,但是它的运行结果是,下一行比当前代码行数大的位置。有时,unt更加方便和便捷使用,让我们在下面案例中展示,首先给出使用语法:

取决于我们是否输入代码行数参数lineno,unt命令可以按照下面两种方式运行:

  • 没有lineno,代码可以继续执行到,下一次行数比当前行数大的位置,这就相似于n(next),也就是类似于执行“step over”的另一种形式。但是,命令n和unt的不同点是,unt只会在下一次行数比当前行数大的位置停止,而n命令会停在下一个逻辑执行行。
  • 具有lineno,代码会运行到下一次行数比当前行数大或者相等的位置,这就类似于c(continue)后面带有一个代码函数参数。

上面两种情况下,unt命令类似于n(next)和s(step)都只会停止在当前帧(或函数)。

当你想继续执行并在当前源文件中更远的地方停止时,请使用 unt。 你可以将其视为 n (next) 和 b (break) 的混合体,具体取决于是否传递行号参数。

在下面的示例中,有一个带有循环的函数。 在这里,我们希望继续执行代码并在循环后停止,而不是单步执行循环的每次迭代或设置断点,下面是文件codeExample4unt.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输出如下:

$ ./codeExample4unt.py
> /code/codeExample4unt.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/codeExample4unt.py(10)get_path()
-> for char in tail:
(Pdb)
> /code/codeExample4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb)
> /code/codeExample4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'codeExample4unt.py')

ll命令首先用于打印函数的源代码,然后是 unt。 pdb 记得上次输入的命令,所以我只是按 Enter 重复 unt 命令。 这将继续执行代码,直到到达比当前行大的源代码行。

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

6.显示表达式

类似于打印表达式p和pp的功能,我们可以使用dispaly [expression]来告诉pdb来显示一个表达的值,同样适用undisplay [expression]来清楚一个表达式显示,下面使用一些使用语法解释:

下面是一个案例,代码文件为codeExample4display.py,展示了一个循环的使用:

$ ./codeExample4display.py
> /code/codeExample4display.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/codeExample4display.py:11
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb)
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb)
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

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

你可以多次输入 display 以构建表达式监视列表。 这比 p 更容易使用。 添加你感兴趣的所有表达式后,只需输入 display 即可查看当前值:

$ ./codeExample4display.py
> /code/codeExample4display.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/codeExample4display.py:11
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './codeExample4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'codeExample4display.py'
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './codeExample4display.py'
head: '.'
tail: 'codeExample4display.py'

7.Python调用者ID

在最后一节,我们会基于上述学习到的内容,来演示“call ID”的功能使用。下面是案例代码codeExample5.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(where)来打印一个代码栈序列,就是从低到上显示所有堆栈:

$ ./codeExample5.py
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/codeExample5.py(12)<module>()
-> filename_path = get_file_info(filename)
  /code/codeExample5.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() 用于在函数 get_path() 中输入 pdb。 在此示例中,调用函数 get_path() 的源代码行是:

-> file_path = fileutil.get_path(full_fname)

在每个 -> 上面的行包含文件名和代码行数(在括号中),以及函数名称,所以调用者就是:

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

显然在这个简单的样例中,我们展示了如何查找函数调用者,但是想象一个大型应用程序,您在其中设置了一个带有条件的断点,以确定错误输入值的来源,下面让我们进一步深入。

什么是堆栈追踪和栈帧内容?

堆栈跟踪只是 Python 为跟踪函数调用而创建的所有帧的列表。 框架是 Python 在调用函数时创建并在函数返回时删除的数据结构。 堆栈只是在任何时间点的帧或函数调用的有序列表。 (函数调用)堆栈在应用程序的整个生命周期中随着函数被调用然后返回而增长和缩小。打印时,这个有序的帧列表,即堆栈,称为堆栈跟踪。 你可以随时通过输入命令 w 来查看它,就像我们在上面找到调用者一样。

当前栈帧什么意思?

将当前帧视为 pdb 已停止执行的当前函数。 换句话说,当前帧是你的应用程序当前暂停的位置,并用作 pdb 命令(如 p(打印))的“参考帧”。p 和其他命令将在需要时使用当前帧作为上下文。 在 p 的情况下,当前帧将用于查找和打印变量引用。当 pdb 打印堆栈跟踪时,箭头 > 表示当前帧。

怎么使用和切换栈帧?

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

让我们看一个使用 u 和 d 命令的例子。 在这种情况下,我们要检查codeExample5.py 中函数 get_file_info() 的局部变量 full_fname。 为此,我们必须使用命令 u 将当前帧向上更改一级:

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

因为是在fileutil.py文件函数get_path()中设置的断点程序pdb.set_trace(),所以当前帧被设置在此处,如下面所示:

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

为了访问和打印在codeExample5.py文件函数get_file_info()里的变量full_fname,命令u实现移动到前一个栈帧:

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

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

继续这个例子,在打印 full_fname 之后,使用 d 将当前帧移动到其原始位置,并打印 get_path() 中的局部变量 fname。如果我们愿意,我们可以通过将 count 参数传递给 u 或 d 来一次移动多个帧。 例如,我们可以通过输入 u 2 移动到 codeExample5.py 中的模块级别:

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

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

8.总结

本篇教程中,我们主要讲解了pdb中一些基本常用的内容:

打印表达式使用n(next)和s(step)命令调试代码断点使用使用unt(until)来继续执行代码显示表达式查找一个函数的调用者

最后,希望这篇教程对你带来帮助,本篇教程中对应的代码可以在代码仓库中下载:https://github.com/1311440131/pdb_basic_tutorial

到此这篇关于如何使用pdb进行Python调试的文章就介绍到这了,更多相关pdb Python调试内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • python的pdb调试命令的命令整理及实例

    python的pdb调试命令的命令整理及实例 一.命令整理 pdb调试命令 完整命令 简写命令 描述 args a 打印当前函数的参数 break b 设置断点 clear cl 清除断点 condition 无 设置条件断点 continue c或者cont 继续运行,知道遇到断点或者脚本结束 disable 无 禁用断点 enable 无 启用断点 help h 查看pdb帮助 ignore 无 忽略断点 jump j 跳转到指定行数运行 list l 列出脚本清单 next n 执行下条语

  • 总结用Pdb库调试Python的方式及常用的命令

    用Pdb调试有多种方式 使用 Pdb调试 Python的程序的方式主要是下面的三种!下面逐一介绍 命令行加-m参数 命令行启动目标程序,加上-m参数,这样调用 testPdb.py的话断点就是程序执行的第一行之前 本文接下来重点讲到的实例展示就是使用这种方式进行调试的! python -m pdb testPdb.py 在python交互环境调试 >>> import pdb >>> import testPdb >>> pdb.run('testPd

  • 使用pdb模块调试Python程序实例

    在Python中,语法错误可以被Python解释器发现,但逻辑上错误或变量使用错误却不容易发现,如果结果没有符合预期,则需要调试,一个很好的调试工具:Python自带的pdb模块.pdb是Python自带的调试模块.使用pdb模块可以为脚本设置断点.单步执行.查看变量值等. pdb可以用命令行参数的方式启动,也可以使用import 将其导入后再使用. 复制代码 代码如下: >>> dir(pdb)  ['Pdb', 'Repr', 'Restart', 'TESTCMD',.....,'

  • 使用Python中PDB模块中的命令来调试Python代码的教程

    你有多少次陷入不得不更改别人代码的境地?如果你是一个开发团队的一员,那么你遇到上述境地的次数比你想要的还要多.然而,Python中有一个整洁的调试特性(像其他大多数语言一样),在这种情况下使用非常方便.本文是一篇快速教程,希望它能让你的编码生活更加容易. 1. 一个混乱的程序 出于本教程的目的,让我们研究一下下面的简单程序. 这个程序接收两个命令行参数,然后执行加法和减法操作. (假设用户输入的是有效值,因此代码中我们没有进行错误处理.) import sys def add(num1=0, n

  • python pdb调试方法分享

    复制代码 代码如下: import pdb def pdb_test(arg):    for i in range(arg):        print(i)    return arg pdb.run("pdb_test(3)") b 函数名.行号: 打断点,b可以查询所有的断点. 复制代码 代码如下: (Pdb) b pdb_testBreakpoint 1 at c:\users\plpcc\desktop\pdbtest.py:3(Pdb) bNum Type        

  • 使用PDB简单调试Python程序简明指南

    在 Python 中也可以像 gcc/gdb 那样调试程序,只要在运行 Python 程序时引入 pdb 模块(假设要调试的程序名为 d.py): 复制代码 代码如下: $ vi d.py #!/usr/bin/python def main():         i, sum = 1, 0         for i in xrange(100):                 sum = sum + i         print sum if __name__ == '__main__'

  • 如何使用pdb进行Python调试

    调试应用有时是一个不受欢迎的工作,当你长期编码之后,只希望写的代码顺利运行.但是,很多情况下,我们需要学习一个新的语言功能或者实验检测新的方法,从而去理解其中运行的机制原理. 即使不考虑这样的场景,调试代码仍然是有必要的,所以学会在工作中使用调试器是很重要的.本篇教程中,我将会给出基本的使用关于pdb----Python's interative source code debugger. 首先给出一些pdb的基础知识,大家可以保存这篇文章方便后续的阅读.pdb类似于其他的调试器,是独立的工具,

  • python 调试器pdb的简单使用

    使用PDB的方式有两种: 1. 单步执行代码,通过命令 python -m pdb xxx.py 启动脚本,进入单步执行模式 pdb命令行: 1)进入命令行Debug模式,python -m pdb xxx.py 2)h:(help)帮助 3)w:(where)打印当前执行堆栈 4)d:(down)执行跳转到在当前堆栈的深一层(个人没觉得有什么用处) 5)u:(up)执行跳转到当前堆栈的上一层 6)b:(break)添加断点 b 列出当前所有断点,和断点执行到统计次数 b line_no:当前脚

  • python 调试冷知识(小结)

    对于 python 代码的调试我们通常都是使用 IDE 自带的调试功能.但是 IDE 提供的调试功能存在局限性,例如在测试服务器上调试代码,但是又不可能在测试服务器上安装 IDE 进行调试.这时我们就可以利用下面所讲解的三个工具进行调试. 零.准备调试代码 在讲解三个调试工具前,我们先编写待调试的代码.代码很简单,就是计算两个数的商.我们在编写代码的时候故意留下了除数为 0 的 bug. def division(start, end): for i in range(start, end, -

  • VSCode下配置python调试运行环境的方法

    VSCode配置python调试环境 很久之前的一个东东,翻出来看看 VSCode配置python调试环境 * 1.下载python解释器 * 2.在VSCode市场中安装Python插件 * 4.在用户设置里加两条 * 5.接下来是正式的调试了 1080 两个数的平方和 Input Output Input示例 Output示例 1.下载python解释器 python 3.6.3 for windows 安装到系统某个路径例如C:\Python36 最好添加到Path,也可以不加 2.在VS

  • python调试模式无响应解决案例

     页面是这样的我尝试了很多次,都不响应.代码是这样的 import xlrd ###导入数据 def read_data(workbook, col_index): input_datas = [] wb = workbook for year in wb.sheet_names(): sheet = wb.sheet_by_name(year) cols = sheet.col_values(col_index) for col in cols: input_datas.append(col)

  • 详解Python调试神器之PySnooper

    相信很多程序员在调试代码时,都用过 print.代码少还好说,如果是大型项目,面对众多 print 的输出结果,可能要头大了. 今天推荐一个 GitHub 热门开源项目:PySnooper.该项目推出的第一天就收获 2000+ Star,登上了 GitHub 日榜第一位,如今有近 15k Star.可见这是一款 Python 开发者喜欢的工具.欢迎收藏学习.喜欢点赞支持,文末技术交流可以畅聊! 链接:https://github.com/cool-RR/PySnooper PySnooper 是

  • python调试模块ipdb详解

    目录 1. 调试python 1.1 使用ipdb 1.2 常用命令 1. 调试python ipdb是用来python中用以交互式debug的模块,可以直接利用pip安装; 其功能类似于pycharm中 python控制台,而使用ipdb 的优点,便是直接在代码中调试,避免了在python控制台,或者重新设置一些简单变量. pip install ipdb 1.1 使用ipdb 当程序运行到ipdb.set_trace()的地方会自动进入debug模式. for i in range(5):

  • c调用python调试方法

    C语言可以调用python,C如何调用python呢?调用后如何调试呢?小编与大家分享操作经验. (一)C语言调用python 首先,C语言中调用python,要使用头文件Python.h. 2.接着,定义一个调用python的函数. 3.函数中,设置python库的路径. 4.然后,初始化python. 5.运行一个python代码,输出How are you. 6.最后,释放python. 7.(二)调试程序 调试前,先单击文件菜单中的保存选项,保存程序. 8.接着,单击运行菜单中的编译命令

  • python调试神器PySnooper的使用

    相信很多小伙伴平时写python的时候都是需要调试程序的,出问题了,需要了解函数内部是怎么跑的,而这个时候很多人都会想到在疑惑的地方使用print函数来打印一下参数来调试.虽然用print也是不失为是一种方法,但是有时如果疑惑的地方多就要每个地方都要加print,这样就显得比较麻烦了. 今天发现在Github开源了一个神器,可以清楚让你清楚了解函数内部的运行以及参数值的变化,PySnooper,项目地址:https://github.com/cool-RR/PySnooper 使用简单,强大,谁

  • 200个Python 标准库总结

    目录 1.文本 2.数学 3.函数式编程 4.文件与目录 5.持久化 6.压缩 7.加密 8.操作系统工具 9.并发 10.进程间通信 11.互联网 12.互联网协议与支持 13.多媒体 14.国际化 15.编程框架 16.Tk图形用户接口 17.开发工具 18.调试 19.运行时 20.解释器 21.导入模块 22.Python语言 23.其他 24.Windows相关 25.Unix相关 1.文本 string:通用字符串操作 re:正则表达式操作 difflib:差异计算工具 textwr

随机推荐