17.8. pdb — 交互式调试器

目的: Python 的交互式调试器

pdb 为 Python 程序实现了一个交互式的调试环境。它的特性包括暂停程序,查看变量值和单步调试,从而让你能够理解程序的行为并且找到逻辑上的错误

打开调试器

使用 pdb 的第一步是让解释器在正确的时间进入调试器。根据初始条件和被调试程序的不同,有几种不同的方式进入调试器。

通过命令行

使用调试器最直接的方法是命令行。将需要运行的程序作为输入从而让命令行知道需要运行什么。

pdb_script.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

class MyObj:

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.count):
            print(i)
        return

if __name__ == '__main__':
    MyObj(5).go()

从命令行运行调试器使得它载入源代码并且停止在程序的第一个声明。在这个例子中,调试器停止在运行 MyObj 类定义的第八行之前。

$ python3 -m pdb pdb_script.py

> .../pdb_script.py(8)<module>()
-> class MyObj(object):
(Pdb)

注:

一般而言,pdb 在打印文件名时,输出中包括了到达每个模块的完整路径。为了保持例子的简洁,例子中输出的路径被替换为了省略号(...)。

解释器里

很多 Python 的开发者在开发模块的早期版本时会使用交互式的解释器,因为这让他们能够迭代地开发,而不需要保存,运行,重复这样的循环。如果想让调试器在交互式的解释器中运行,需要 run() 或者 runeval() 命令。

$ python3

Python 3.5.1 (v3.5.1:37a07cee5969, Dec  5 2015, 21:12:44)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb_script
>>> import pdb
>>> pdb.run('pdb_script.MyObj(5).go()')
> <string>(1)<module>()
(Pdb)

run() 的参数是一个可以被 Python 解释器运行的字符串表达式。调试器会分析它,然后停止在第一个表达式运行之前。这里描述的解释器可以用来引导和控制程序的运行。

在程序内调试

所有之前的例子都是在程序开始时进行调试。对于运行过程很长的一些程序,问题往往发生于程序执行后的很长一段时间。此时如果使用 set_trace() 在程序内部调试将会非常方便。

pdb_set_trace.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

class MyObj:

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.count):
            pdb.set_trace()
            print(i)
        return

if __name__ == '__main__':
    MyObj(5).go()

代码样本的第17行触发了位于执行位点上的调试器,并在18行上将其暂停。

$ python3 ./pdb_set_trace.py

> .../pdb_set_trace.py(18)go()
-> print(i)
(Pdb)

set_trace() 只是一个 Python 函数,因此它能在程序的任何部位被调用。这使得基于程序内部条件的调试器得以被创建,其中也包括意外句柄和控制状态的特定分支语句。

故障发生后

在程序终止后调试检测出故障被称为 post-mortem 调试。 pdb 通过 pm() 和 post_mortem() 等内置函数来支持这类调试。

pdb_post_mortem.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

class MyObj:

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.num_loops):
            print(i)
        return

本例中位于第14行的错误属性值出发了 AttributeError 意外,导致了程序执行终止。 pm() 找出了程序的主动回溯记录并开始在意外发生处的回调栈内的位点上的调试器。

$ python3
Python 3.5.1 (v3.5.1:37a07cee5969, Dec  5 2015, 21:12:44)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pdb_post_mortem import MyObj
>>> MyObj(5).go()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../pdb_post_mortem.py", line 14, in go
    for i in range(self.num_loops):
AttributeError: 'MyObj' object has no attribute 'num_loops'
>>> import pdb
>>> pdb.pm()
> .../pdb/pdb_post_mortem.py(14)go()
-> for i in range(self.num_loops):
(Pdb)

控制调试器

调试器的接口是一种小型命令语言,可让您在调用堆栈中移动、检查和更改变量的值,并控制调试器如何执行程序。 交互式调试器使用 readline 接受命令,并支持命令、文件名和函数名称的 Tab 自动完成。 输入一个空行再次重新运行上一个命令,除非它是 list 操作。

导航执行堆栈

在调试器运行的任何时候使用 where(缩写为 w ) 来确切地找出正在执行的行以及程序所在的调用堆栈的位置。 在这种情况下, go() 方法中的模块 pdb_set_trace.py 第 18 行。

$ python3 pdb_set_trace.py
> .../pdb_set_trace.py(18)go()
-> print(i)
(Pdb) where
  .../pdb_set_trace.py(22)<module>()
-> MyObj(5).go()
> .../pdb_set_trace.py(18)go()
-> print(i)
(Pdb)

要在当前位置周围添加更多上下文,请使用 listl )。

(Pdb) l
 13          self.count = num_loops
 14
 15      def go(self):
 16          for i in range(self.count):
 17              pdb.set_trace()
 18  ->          print(i)
 19          return
 20
 21  if __name__ == '__main__':
 22      MyObj(5).go()
[EOF]
(Pdb)

认设置是列出当前行周围的 11 行(前五行和后五行)。 使用带有单个数字参数的 list 列出该行周围的 11 行而不是当前行。

(Pdb) list 14
  9
 10  class MyObj(object):
 11
 12      def __init__(self, num_loops):
 13          self.count = num_loops
 14
 15      def go(self):
 16          for i in range(self.count):
 17              pdb.set_trace()
 18  ->          print(i)
 19          return

如果 list 接收到两个参数,它会将它们解释为要包含在其输出中的第一行和最后一行。

(Pdb) list 7, 19
  7  import pdb
  8
  9
 10  class MyObj(object):
 11
 12      def __init__(self, num_loops):
 13          self.count = num_loops
 14
 15      def go(self):
 16          for i in range(self.count):
 17              pdb.set_trace()
 18  ->          print(i)
 19          return

longlistll )命令打印当前函数或帧的源,而不必事先确定行号。 该命令是 longlist ,因为对于长函数,它可能产生比 list 的默认值更多的输出。

(Pdb) longlist
 15      def go(self):
 16          for i in range(self.count):
 17              pdb.set_trace()
 18  ->          print(i)
 19          return

source 命令加载并打印任意类,函数或模块的完整源代码。

(Pdb) source MyObj
 10  class MyObj:
 11
 12      def __init__(self, num_loops):
 13          self.count = num_loops
 14
 15      def go(self):
 16          for i in range(self.count):
 17              pdb.set_trace()
 18              print(i)
 19          return

使用 updown 在当前调用堆栈中的帧之间移动。 up (缩写为 u )向堆栈中的旧帧移动。 downd )向更新的帧移动。 每次向上或向下移动堆栈时,调试器都会以与 where 生成的格式相同的格式打印当前位置。

(Pdb) up
> .../pdb_set_trace.py(22)<module>()
-> MyObj(5).go()

(Pdb) down
> .../pdb_set_trace.py(18)go()
-> print(i)

将一个数字参数传递给 updown ,一次向上或向下移动多步。

检查堆栈上的变量

堆栈中的每个帧都维护一组变量,包括正在执行的函数的本地值和全局状态信息。 pdb 提供了几种检查这些变量内容的方法。

pdb_function_arguments.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

def recursive_function(n=5, output='to be printed'):
    if n > 0:
        recursive_function(n - 1)
    else:
        pdb.set_trace()
        print(output)
    return

if __name__ == '__main__':
    recursive_function()

args 命令(缩写为 a )打印当前帧中活动的函数的所有参数。 此示例还使用递归函数来显示由 where 打印时更深的堆栈的外观。

$ python3 pdb_function_arguments.py
> .../pdb_function_arguments.py(15)recursive_function()
-> print(output)
(Pdb) where
  .../pdb_function_arguments.py(19)<module>()
-> recursive_function()
  .../pdb_function_arguments.py(12)recursive_function()
-> recursive_function(n - 1)
  .../pdb_function_arguments.py(12)recursive_function()
-> recursive_function(n - 1)
  .../pdb_function_arguments.py(12)recursive_function()
-> recursive_function(n - 1)
  .../pdb_function_arguments.py(12)recursive_function()
-> recursive_function(n - 1)
  .../pdb_function_arguments.py(12)recursive_function()
-> recursive_function(n - 1)
> .../pdb_function_arguments.py(15)recursive_function()
-> print(output)

(Pdb) args
n = 0
output = to be printed

(Pdb) up
> .../pdb_function_arguments.py(12)recursive_function()
-> recursive_function(n - 1)

(Pdb) args
n = 1
output = to be printed

p 命令计算作为参数给出的表达式并打印结果。 Python 的 print() 函数也可用,但它被传递给解释器以执行而不是在调试器中作为命令运行。

(Pdb) p n
1

(Pdb) print(n)
1

类似地,在表达式前加上 ! 会将它传递给要解析的Python解释器。 此功能可用于执行任意 Python 语句,包括修改变量。 此示例在让调试器继续运行程序之前更改 output 的值。 调用 set_trace() 之后的下一个语句打印 output 的值,显示修改后的值。

$ python3 pdb_function_arguments.py

> .../pdb_function_arguments.py(14)recursive_function()
-> print(output)

(Pdb) !output
'to be printed'

(Pdb) !output='changed value'

(Pdb) continue
changed value

对于更复杂的值,如嵌套或大型数据结构,使用 pp 来「漂亮地打印」它们。 该程序从文件中读取几行文本。

pdb_pp.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

with open('lorem.txt', 'rt') as f:
    lines = f.readlines()

pdb.set_trace()

p 打印变量 lines 会导致难以阅读的输出,因为它可能会笨拙地换行。 pp 使用 pprint 格式化清洁打印的值。

$ python3 pdb_pp.py

> .../pdb_pp.py(12)<module>()->None
-> pdb.set_trace()
(Pdb) p lines
['Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
\n', 'Donec egestas, enim et consecte tuer ullamcorper, lect
us \n', 'ligula rutrum leo, a elementum el it tortor eu quam
.\n']

(Pdb) pp lines
['Lorem ipsum dolor sit amet, consectetuer adipiscing elit. \n',
 'Donec egestas, enim et consectetuer ullamcorper, lectus \n',
 'ligula rutrum leo, a elementum elit tortor eu quam.\n']

(Pdb)

对于交互式探索和实验,可以从调试器中删除标准的 Python 交互式提示,其中已填充当前帧中的全局变量和局部变量。

$ python3 -m pdb pdb_interact.py
> .../pdb_interact.py(7)<module>()
-> import pdb
(Pdb) break 14
Breakpoint 1 at .../pdb_interact.py:14

(Pdb) continue
> .../pdb_interact.py(14)f()
-> print(l, m, n)

(Pdb) p l
['a', 'b']

(Pdb) p m
9

(Pdb) p n
5

(Pdb) interact
*interactive*

>>> l
['a', 'b']

>>> m
9

>>> n
5

可以从交互式解释器更改 list 等可变对象。 不可变对象不能改变,并且名称不能绑定到新值。

>>> l.append('c')
>>> m += 7
>>> n = 3

>>> l
['a', 'b', 'c']

>>> m
16

>>> n
3

使用文件结束序列 Ctrl-D 退出交互式提示并返回调试器。 在这个例子中,列表 l 已经改变,但是 mn 的值未改变。

>>> ^D

(Pdb) p l
['a', 'b', 'c']

(Pdb) p m
9

(Pdb) p n
5

(Pdb)

逐步完成一个程序

除了在程序暂停时向上和向下定位调用堆栈之外,还可以从进入调试器的点逐步执行程序。

pdb_step.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

def f(n):
    for i in range(n):
        j = i * n
        print(i, j)
    return

if __name__ == '__main__':
    pdb.set_trace()
    f(5)

使用 step (缩写为 s )执行当前行,然后在下一个执行点停止——调用函数内的第一个语句或当前函数的下一行。

$ python3 pdb_step.py

> .../pdb_step.py(18)<module>()
-> f(5)

解释器在调用 set_trace() 后暂停,并将控制权交给调试器。 第一个 step 导致执行进入 f()

(Pdb) step
--Call--
> .../pdb_step.py(10)f()
-> def f(n):

再次 step 将执行移动到 f() 的第一行并启动循环。

(Pdb) step
> .../pdb_step.py(11)f()
-> for i in range(n):

再次步进移动到循环内的第一行,其中定义了 j

(Pdb) step
> .../pdb_step.py(12)f()
-> j = i * n

(Pdb) p i
0

i 的值是 0 ,所以在再一步之后, j 的值也应该是 0

(Pdb) step
> .../pdb_step.py(13)f()
-> print(i, j)

(Pdb) p j
0

(Pdb)

如果在发生错误的点之前有大量代码要覆盖,或者重复调用相同的函数,则以这种方式一次步进一行可能会变得乏味。

pdb_next.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import pdb

def calc(i, n):
    j = i * n
    return j

def f(n):
    for i in range(n):
        j = calc(i, n)
        print(i, j)
    return

if __name__ == '__main__':
    pdb.set_trace()
    f(5)

在这个例子中, calc() 没有任何问题,所以每次在 f() 中的循环中调用它时,将 calc() 的所有行在当他们被执行时显示出来,掩盖了有用的输出。

$ python3 pdb_next.py

> .../pdb_next.py(23)<module>()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(15)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(16)f()
-> for i in range(n):

(Pdb) step
> .../pdb_next.py(17)f()
-> j = calc(i, n)

(Pdb) step
--Call--
> .../pdb_next.py(10)calc()
-> def calc(i, n):

(Pdb) step
> .../pdb_next.py(11)calc()
-> j = i * n

(Pdb) step
> .../pdb_next.py(12)calc()
-> return j

(Pdb) step
--Return--
> .../pdb_next.py(12)calc()->0
-> return j

(Pdb) step
> .../pdb_next.py(18)f()
-> print(i, j)

(Pdb) step
0 0

> .../pdb_next.py(16)f()
-> for i in range(n):
(Pdb)

next 命令(缩写为 n )类似于 step ,但不进入从正在执行的语句调用的函数。 实际上,它在单个操作中步进函数调用中的所有细节,到当前函数的下一个语句。

> .../pdb_next.py(16)f()
-> for i in range(n):
(Pdb) step
> .../pdb_next.py(17)f()
-> j = calc(i, n)

(Pdb) next
> .../pdb_next.py(18)f()
-> print(i, j)

(Pdb)

until 命令就像 next ,除非它明确地继续,直到执行到达同一函数中的一行,其行号高于当前值。 这意味着,例如, until 可用于跳到循环的结束。

$ python3 pdb_next.py

> .../pdb_next.py(23)<module>()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(15)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(16)f()
-> for i in range(n):

(Pdb) step
> .../pdb_next.py(17)f()
-> j = calc(i, n)

(Pdb) next
> .../pdb_next.py(18)f()
-> print(i, j)

(Pdb) until
0 0
1 5
2 10
3 15
4 20
> .../pdb_next.py(19)f()
-> return

(Pdb)

在运行 until 命令之前,当前行是 18 ,循环的最后一行。 在 until 运行之后,执行到第 19 行,并且循环已经结束。

要让执行运行到特定行,请将行号传递给 until 命令。 与设置断点时不同,传递给 until 的行号必须高于当前行号,因此在函数内定位以跳过长块最有用。

$ python3 pdb_next.py
> .../pdb_next.py(23)<module>()
-> f(5)
(Pdb) list
 18          print(i, j)
 19      return
 20
 21  if __name__ == '__main__':
 22      pdb.set_trace()
 23  ->    f(5)
[EOF]

(Pdb) until 18
*** "until" line number is smaller than current line number

(Pdb) step
--Call--
> .../pdb_next.py(15)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(16)f()
-> for i in range(n):

(Pdb) list
 11      j = i * n
 12      return j
 13
 14
 15  def f(n):
 16  ->    for i in range(n):
 17          j = calc(i, n)
 18          print(i, j)
 19      return
 20
 21  if __name__ == '__main__':

(Pdb) until 19
0 0
1 5
2 10
3 15
4 20
> .../pdb_next.py(19)f()
-> return

(Pdb)

return 命令是绕过函数部分的另一个捷径。 它继续执行,直到函数即将执行 return 语句,然后它暂停,提供时间在函数返回之前查看返回值。

$ python3 pdb_next.py

> .../pdb_next.py(23)<module>()
-> f(5)
(Pdb) step
--Call--
> .../pdb_next.py(15)f()
-> def f(n):

(Pdb) step
> .../pdb_next.py(16)f()
-> for i in range(n):

(Pdb) return
0 0
1 5
2 10
3 15
4 20
--Return--
> .../pdb_next.py(19)f()->None
-> return

(Pdb)

断点

随着程序变长,即使使用 nextuntil 也会变得缓慢而繁琐。 一个更好的解决方案是让它正常运行,直到达到调试器应该中断的程度,而不是手动执行程序。 set_trace() 可以启动调试器,但只有在程序中有一个点应该暂停的情况下才有效。 通过调试器运行程序更方便,但是使用 断点 告诉调试器提前停止的位置。 调试器监视程序,当它到达断点描述的位置时,程序在执行行之前暂停。

pdb_break.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

def calc(i, n):
    j = i * n
    print('j =', j)
    if j > 0:
        print('Positive!')
    return j

def f(n):
    for i in range(n):
        print('i =', i)
        j = calc(i, n)  # noqa
    return

if __name__ == '__main__':
    f(5)

用于设置断点的 break 命令(缩写为 b )有几个选项,包括处理应暂停的行号,文件和函数。 要在当前文件的特定行上设置断点,请使用 break lineno

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 12
Breakpoint 1 at .../pdb_break.py:12

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(12)calc()
-> print('Positive!')

(Pdb)

命令 continue (缩写为 c )告诉调试器继续运行程序直到下一个断点。 在这种情况下,它在 f() 中运行 for 循环的第一次迭代,并在第二次迭代期间在 calc() 内停止。

通过指定函数名称而不是行号,也可以将断点设置为函数的第一行。 此示例显示了为 calc() 函数添加断点时会发生什么。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:8

(Pdb) continue
i = 0
> .../pdb_break.py(9)calc()
-> j = i * n

(Pdb) where
  .../pdb_break.py(23)<module>()
-> f(5)
  .../pdb_break.py(19)f()
-> j = calc(i, n)
> .../pdb_break.py(9)calc()
-> j = i * n

(Pdb)

要在另一个文件中指定断点,请在行或函数参数前加上文件名。

pdb_break_remote.py

#!/usr/bin/env python3
# encoding: utf-8

from pdb_break import f

f(5)

在启动主程序 pdb_break_remote.py 之后,在这里为 pdb_break.py 的第 12 行设置断点。

$ python3 -m pdb pdb_break_remote.py

> .../pdb_break_remote.py(4)<module>()
-> from pdb_break import f
(Pdb) break pdb_break.py:12
Breakpoint 1 at .../pdb_break.py:12

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(12)calc()
-> print('Positive!')

(Pdb)

文件名可以是源文件的完整路径,也可以是 sys.path 上可用文件的相对路径。

要列出当前设置的断点,请使用不带任何参数的 break 。 输出包括每个断点的文件和行号,以及有关遇到的次数的信息。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 12
Breakpoint 1 at .../pdb_break.py:12

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:12

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb/pdb_break.py(12)calc()
-> print('Positive!')

(Pdb) continue
Positive!
i = 2
j = 10
> .../pdb_break.py(12)calc()
-> print('Positive!')

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:12
        breakpoint already hit 2 times

(Pdb)

管理断点

在添加每个新断点时,会为其分配一个数字标识符。 ID 用于以交互方式启用、禁用和删除断点。 使用 disable 关闭断点会告诉调试器在达到该行时不要停止。 断点会被记住,但会被忽略。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:8

(Pdb) break 12
Breakpoint 2 at .../pdb_break.py:12

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:8
2   breakpoint   keep yes   at .../pdb_break.py:12

(Pdb) disable 1

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep no    at .../pdb_break.py:8
2   breakpoint   keep yes   at .../pdb_break.py:12

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
> .../pdb_break.py(12)calc()
-> print('Positive!')

(Pdb)

下一个调试会话在程序中设置两个断点,然后禁用一个断点。 程序运行直到遇到剩余的断点,然后在执行继续之前使用 enable 重新打开另一个断点。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:8

(Pdb) break 18
Breakpoint 2 at .../pdb_break.py:18

(Pdb) disable 1

(Pdb) continue
> .../pdb_break.py(18)f()
-> print('i =', i)

(Pdb) list
 13      return j
 14
 15
 16  def f(n):
 17      for i in range(n):
 18 B->      print('i =', i)
 19          j = calc(i, n)
 20      return
 21
 22  if __name__ == '__main__':
 23      f(5)

(Pdb) continue
i = 0
j = 0
> .../pdb_break.py(18)f()
-> print('i =', i)

(Pdb) list
 13      return j
 14
 15
 16  def f(n):
 17      for i in range(n):
 18 B->      print('i =', i)
 19          j = calc(i, n)
 20      return
 21
 22  if __name__ == '__main__':
 23      f(5)

(Pdb) p i
 1

(Pdb) enable 1
Enabled breakpoint 1 at .../pdb_break.py:8

(Pdb) continue
i = 1
> .../pdb_break.py(9)calc()
-> j = i * n

(Pdb) list
  4  # Copyright (c) 2010 Doug Hellmann.  All rights reserved.
  5  #
  6
  7
  8 B   def calc(i, n):
  9  ->    j = i * n
 10        print('j =', j)
 11        if j > 0:
 12            print('Positive!')
 13        return j
 14

(Pdb)

list 输出中以 B 为行的前缀显示程序中断点的位置(第 8 行和第 18 行)。

使用 clear 完全删除断点。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break calc
Breakpoint 1 at .../pdb_break.py:8

(Pdb) break 12
Breakpoint 2 at .../pdb_break.py:12

(Pdb) break 18
Breakpoint 3 at .../pdb_break.py:18

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:8
2   breakpoint   keep yes   at .../pdb_break.py:12
3   breakpoint   keep yes   at .../pdb_break.py:18

(Pdb) clear 2
Deleted breakpoint 2

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:8
3   breakpoint   keep yes   at .../pdb_break.py:18

(Pdb)

其他断点保留其原始标识符,不重新编号。

临时断点

临时断点在程序执行第一次命中时会自动清除。 使用临时断点可以很容易地快速到达程序流程中的特定位置,就像使用常规断点一样,但是由于它会立即清除,因此如果程序的该部分重复运行,则不会干扰后续过程。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) tbreak 12
Breakpoint 1 at .../pdb_break.py:12

(Pdb) continue
i = 0
j = 0
i = 1
j = 5
Deleted breakpoint 1 at .../pdb_break.py:12
> .../pdb_break.py(12)calc()
-> print('Positive!')

(Pdb) break

(Pdb) continue
Positive!
i = 2
j = 10
Positive!
i = 3
j = 15
Positive!
i = 4
j = 20
Positive!
The program finished and will be restarted
> .../pdb_break.py(8)<module>()
-> def calc(i, n):

(Pdb)

程序第一次到达第 12 行后,断点将被删除,执行不会再次停止,直到程序结束。

条件断点

可以将规则应用于断点,以便仅在满足条件时停止执行。 使用条件断点可以更好地控制调试器暂停程序的方式,而不是手动启用和禁用断点。 条件断点可以通过两种方式设置。 第一种是使用 break 设置断点时的条件。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 10, j>0
Breakpoint 1 at .../pdb_break.py:10

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:10
        stop only if j>0

(Pdb) continue
i = 0
j = 0
i = 1
> .../pdb_break.py(10)calc()
-> print('j =', j)

(Pdb)

condition 参数必须是使用在定义断点的堆栈帧中可见的值的表达式。 如果表达式的计算结果为 true ,则执行将在断点处停止。

条件也可以使用 condition 命令应用于现有断点。 参数是断点 id 和表达式。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 10
Breakpoint 1 at .../pdb_break.py:10

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:10

(Pdb) condition 1 j>0

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:10
        stop only if j>0

(Pdb)

忽略断点

循环或使用对同一函数的大量递归调用的程序时,通常可以通过在执行中跳过来更容易地进行调试,而不是观察每个调用或断点。 ignore 命令告诉调试器在不停止的情况下跳过断点。 每次处理遇到断点时,它都会减少忽略计数器。 当计数器为零时,重新激活断点。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 19
Breakpoint 1 at .../pdb_break.py:19

(Pdb) continue
i = 0
> .../pdb_break.py(19)f()
-> j = calc(i, n)

(Pdb) next
j = 0
> .../pdb_break.py(17)f()
-> for i in range(n):

(Pdb) ignore 1 2
Will ignore next 2 crossings of breakpoint 1.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:19
        ignore next 2 hits
        breakpoint already hit 1 time

(Pdb) continue
i = 1
j = 5
Positive!
i = 2
j = 10
Positive!
i = 3
> .../pdb_break.py(19)f()
-> j = calc(i, n)

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:19
        breakpoint already hit 4 times

将忽略计数显式重置为零会立即重新启用断点。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 19
Breakpoint 1 at .../pdb_break.py:19

(Pdb) ignore 1 2
Will ignore next 2 crossings of breakpoint 1.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:19
        ignore next 2 hits

(Pdb) ignore 1 0
Will stop next time breakpoint 1 is reached.

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_break.py:19

在断点上的触发操作

除了纯粹的交互模式, pdb 还支持基本脚本。 使用 commands ,可以在遇到特定断点时执行一系列解释器命令,包括 Python 语句。 以断点号作为参数运行 commands 后,调试器提示符变为 (com) 。 一次输入一个命令,并使用 end 完成列表以保存脚本并返回主调试器提示符。

$ python3 -m pdb pdb_break.py

> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 10
Breakpoint 1 at .../pdb_break.py:10

(Pdb) commands 1
(com) print('debug i =', i)
(com) print('debug j =', j)
(com) print('debug n =', n)
(com) end

(Pdb) continue
i = 0
debug i = 0
debug j = 0
debug n = 5
> .../pdb_break.py(10)calc()
-> print('j =', j)

(Pdb) continue
j = 0
i = 1
debug i = 1
debug j = 5
debug n = 5
> .../pdb_break.py(10)calc()
-> print 'j =', j

(Pdb)

此功能对于调试使用大量数据结构或变量的代码特别有用,因为可以使调试器自动打印出所有值,而不是每次遇到断点时手动执行。

监视数据变化

还可以在程序执行过程中监视值的变化,而无需使用 display 命令编写显式的 print 命令。

$ python3 -m pdb pdb_break.py
> .../pdb_break.py(8)<module>()
-> def calc(i, n):
(Pdb) break 18
Breakpoint 1 at .../pdb_break.py:18

(Pdb) continue
> .../pdb_break.py(18)f()
-> print('i =', i)

(Pdb) display j
display j: ** raised NameError: name 'j' is not defined **

(Pdb) next
i = 0
> .../pdb_break.py(19)f()
-> j = calc(i, n)  # noqa

(Pdb) next
j = 0
> .../pdb_break.py(17)f()
-> for i in range(n):
display j: 0  [old: ** raised NameError: name 'j' is not defined **]

(Pdb)

每次执行在帧中停止时,将计算表达式,如果它发生更改,则结果将与旧值一起打印。 不带参数的 display 命令打印当前帧的活动显示列表。

(Pdb) display
Currently displaying:
j: 0

(Pdb) up
> .../pdb_break.py(23)<module>()
-> f(5)

(Pdb) display
Currently displaying:

(Pdb)

使用 undisplay 移除所有显示表达式。

(Pdb) display
Currently displaying:
j: 0

(Pdb) undisplay j

(Pdb) display
Currently displaying:

(Pdb)

改变执行流程

jump 命令在运行时改变程序的流程,而不修改代码。 它可以向前跳过以避免运行某些代码,也可以向后跳转以再次运行它。 此示例程序生成一个数字列表。

pdb_jump.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

def f(n):
    result = []
    j = 0
    for i in range(n):
        j = i * n + j
        j += n
        result.append(j)
    return result

if __name__ == '__main__':
    print(f(5))

在没有干扰的情况下运行时,输出是一系列可被 5 整除的数字。

$ python3 pdb_jump.py

[5, 15, 30, 50, 75]

前跳

向前跳转会将执行点移动到当前位置之后,而不会执行其间的任何语句。 通过跳过示例中的第 13 行, j 的值不会增加,并且依赖于它的所有后续值都会小一些。

$ python3 -m pdb pdb_jump.py

> .../pdb_jump.py(8)<module>()
-> def f(n):
(Pdb) break 13
Breakpoint 1 at .../pdb_jump.py:13

(Pdb) continue
> .../pdb_jump.py(13)f()
-> j += n

(Pdb) p j
0

(Pdb) step
> .../pdb_jump.py(14)f()
-> result.append(j)

(Pdb) p j
5

(Pdb) continue
> .../pdb_jump.py(13)f()
-> j += n

(Pdb) jump 14
> .../pdb_jump.py(14)f()
-> result.append(j)

(Pdb) p j
10

(Pdb) disable 1

(Pdb) continue
[5, 10, 25, 45, 70]

The program finished and will be restarted
> .../pdb_jump.py(8)<module>()
-> def f(n):
(Pdb)

后跳

跳转还可以将程序执行移动到已执行的语句,以再次运行它。 这里, j 的值额外增加一次,因此结果序列中的数字都比它们原来的大。

$ python3 -m pdb pdb_jump.py

> .../pdb_jump.py(8)<module>()
-> def f(n):
(Pdb) break 14
Breakpoint 1 at .../pdb_jump.py:14

(Pdb) continue
> .../pdb_jump.py(14)f()
-> result.append(j)

(Pdb) p j
5

(Pdb) jump 13
> .../pdb_jump.py(13)f()
-> j += n

(Pdb) continue
> .../pdb_jump.py(14)f()
-> result.append(j)

(Pdb) p j
10

(Pdb) disable 1

(Pdb) continue
[10, 20, 35, 55, 80]

The program finished and will be restarted
> .../pdb_jump.py(8)<module>()
-> def f(n):
(Pdb)

非法跳转

跳入和退出某些流控制语句是危险的或未定义的,因此调试器不允许这样做。

pdb_no_jump.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

def f(n):
    if n < 0:
        raise ValueError('Invalid n: {}'.format(n))
    result = []
    j = 0
    for i in range(n):
        j = i * n + j
        j += n
        result.append(j)
    return result

if __name__ == '__main__':
    try:
        print(f(5))
    finally:
        print('Always printed')

    try:
        print(f(-5))
    except:
        print('There was an error')
    else:
        print('There was no error')

    print('Last statement')

jump可以用来进入一个函数,但参数没有定义,代码不太可能工作。

$ python3 -m pdb pdb_no_jump.py

> .../pdb_no_jump.py(8)<module>()
-> def f(n):
(Pdb) break 22
Breakpoint 1 at .../pdb_no_jump.py:22

(Pdb) jump 9
> .../pdb_no_jump.py(9)<module>()
-> if n < 0:

(Pdb) p n
*** NameError: NameError("name 'n' is not defined",)

(Pdb) args

(Pdb)

jump 不会进入代码块的中间,例如 for 循环或 try:except语句。

$ python3 -m pdb pdb_no_jump.py

> .../pdb_no_jump.py(8)<module>()
-> def f(n):
(Pdb) break 22
Breakpoint 1 at .../pdb_no_jump.py:22

(Pdb) continue
> .../pdb_no_jump.py(22)<module>()
-> print(f(5))

(Pdb) jump 27
*** Jump failed: can't jump into the middle of a block

(Pdb)

finally 块中的代码必须全部执行,因此 jump 不会离开块。

$ python3 -m pdb pdb_no_jump.py

> .../pdb_no_jump.py(8)<module>()
-> def f(n):
(Pdb) break 24
Breakpoint 1 at .../pdb_no_jump.py:24

(Pdb) continue
[5, 15, 30, 50, 75]
> .../pdb_no_jump.py(24)<module>()
-> print 'Always printed'

(Pdb) jump 26
*** Jump failed: can't jump into or out of a 'finally' block

(Pdb)

最基本的限制是跳转被限制在调用堆栈的底部帧上。 向上移动堆栈以检查变量后,无法更改此时的执行流程。

$ python3 -m pdb pdb_no_jump.py

> .../pdb_no_jump.py(8)<module>()
-> def f(n):
(Pdb) break 12
Breakpoint 1 at .../pdb_no_jump.py:12

(Pdb) continue
> .../pdb_no_jump.py(12)f()
-> j = 0

(Pdb) where
  .../lib/python3.5/bdb.py(
431)run()
-> exec cmd in globals, locals
  <string>(1)<module>()
  .../pdb_no_jump.py(22)<module>()
-> print(f(5))
> .../pdb_no_jump.py(12)f()
-> j = 0

(Pdb) up
> .../pdb_no_jump.py(22)<module>()
-> print(f(5))

(Pdb) jump 25
*** You can only jump within the bottom frame

(Pdb)

重启程序

当调试器到达程序结束时,它会自动将其重新启动,但也可以显式重新启动,而无需离开调试器并丢失当前断点或其他设置。

pdb_run.py

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

import sys

def f():
    print('Command-line args:', sys.argv)
    return

if __name__ == '__main__':
    f()

在调试器中运行此程序以完成打印脚本文件的名称,因为在命令行上没有给出其他参数。

$ python3 -m pdb pdb_run.py

> .../pdb_run.py(7)<module>()
-> import sys
(Pdb) continue

Command line args: ['pdb_run.py']
The program finished and will be restarted
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)

可以使用 run 重新启动程序。 传递给 run 的参数用 shlex 解析并传递给程序,好像它们是命令行参数一样,因此可以使用不同的设置重新启动程序。

(Pdb) run a b c "this is a long value"
Restarting pdb_run.py with arguments:
        a b c this is a long value
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb) continue
Command line args: ['pdb_run.py', 'a', 'b', 'c',
'this is a long value']
The program finished and will be restarted
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)

run 也可用于处理中的任何其他点以重启程序。

$ python3 -m pdb pdb_run.py

> .../pdb_run.py(7)<module>()
-> import sys
(Pdb) break 11
Breakpoint 1 at .../pdb_run.py:11

(Pdb) continue
> .../pdb_run.py(11)f()
-> print('Command line args:', sys.argv)

(Pdb) run one two three
Restarting pdb_run.py with arguments:
        one two three
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)

自定义调试器别名

使用 alias 定义快捷方式,以避免重复键入复杂命令。别名扩展应用于每个命令的第一个单词。 别名的主体可以包含在调试器提示符下输入合法的任何命令,包括其他调试器命令和纯 Python 表达式。 别名定义允许递归,因此一个别名甚至可以调用另一个别名。

$ python3 -m pdb pdb_function_arguments.py

> .../pdb_function_arguments.py(7)<module>()
-> import pdb
(Pdb) break 11
Breakpoint 1 at .../pdb_function_arguments.py:11

(Pdb) continue
> .../pdb_function_arguments.py(11)recursive_function()
-> if n > 0:

(Pdb) pp locals().keys()
dict_keys(['output', 'n'])

(Pdb) alias pl pp locals().keys()

(Pdb) pl
dict_keys(['output', 'n'])

不带任何参数运行 alias 会显示已定义别名的列表。 单个参数被认为是别名的名称,并打印其定义。

(Pdb) alias
pl = pp locals().keys()

(Pdb) alias pl
pl = pp locals().keys()

(Pdb)

使用 %n 引用别名的参数,其中 n 被替换为表示参数位置的数字,从 1 开始。 要使用所有参数,请使用%*

$ python3 -m pdb pdb_function_arguments.py

> .../pdb_function_arguments.py(7)<module>()
-> import pdb
(Pdb) alias ph !help(%1)

(Pdb) ph locals
Help on built-in function locals in module builtins:

locals()
    Return a dictionary containing the current scope's local
    variables.

    NOTE: Whether or not updates to this dictionary will affect
    name lookups in the local scope and vice-versa is
    *implementation dependent* and not covered by any backwards
    compatibility guarantees.

使用 unalias 清除一个别名的定义。

(Pdb) unalias ph

(Pdb) ph locals
*** SyntaxError: invalid syntax (<stdin>, line 1)

(Pdb)

保存配置设置

调试程序包含了大量重复操作:运行代码、观察输出、调整代码或输入,然后再次运行。 pdb 试图减少所需的重复次数来控制调试体验,让你专注于代码而不是调试器。 为了减少向调试器发出相同命令的次数, pdb 可以在启动时读取解释的文本文件中保存的配置。

首先读取文件 ~/.pdbrc ,允许所有调试会话的全局个人首选项。 然后从当前工作目录中读取 ./.pdbrc ,以设置特定项目的本地首选项。

$ cat ~/.pdbrc

# 显示 python 帮助
alias ph !help(%1)
# 覆写别名
alias redefined p 'home definition'

$ cat .pdbrc

# 断点
break 11
# 覆写别名
alias redefined p 'local definition'

$ python3 -m pdb pdb_function_arguments.py

Breakpoint 1 at .../pdb_function_arguments.py:11
> .../pdb_function_arguments.py(7)<module>()
-> import pdb
(Pdb) alias
ph = !help(%1)
redefined = p 'local definition'

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at .../pdb_function_arguments.py:11

(Pdb)

在调试器提示符下键入的任何配置命令都可以保存在其中一个启动文件中。 一些控制执行的命令( continuenext 等)也可以。

$ cat .pdbrc
break 11
continue
list

$ python3 -m pdb pdb_function_arguments.py
Breakpoint 1 at .../pdb_function_arguments.py:11
  6
  7  import pdb
  8
  9
  10  def recursive_function(n=5, output='to be printed'):
  11 B->    if n > 0:
  12            recursive_function(n - 1)
  13        else:
  14            pdb.set_trace()
  15            print(output)
  16        return
> .../pdb_function_arguments.py(11)recursive_function()
-> if n > 0:
(Pdb)

特别有用的是 run ,这意味着调试会话的命令行参数可以在 ./.pdbrc 中设置,因此它们在多次运行中是一致的。

$ cat .pdbrc
run a b c "long argument"

$ python3 -m pdb pdb_run.py
Restarting pdb_run.py with arguments:
      a b c "long argument"
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb) continue
Command-line args: ['pdb_run.py', 'a', 'b', 'c',
'long argument']
The program finished and will be restarted
> .../pdb_run.py(7)<module>()
-> import sys

(Pdb)

另请参阅

  • pdb 标准库文档
  • readline -- 交互式提示编辑库。
  • cmd -- 建立互动程序。
  • shlex -- Shell 命令行解析。
  • Python issue 26053 -- 如果 run 的输出与此处显示的值不匹配,请参阅此 bug 以获取有关 2.7 和 3.5 之间 pdb 输出回归的详细信息。