赞
踩
PDB是Python标准库自带的强大调试器,与其他Python调试器相比,PDB的主要优势在于,它是Python的一部分,PDB几乎可以在任何有Python的地方使用,包括嵌入Python语言的大型系统的专用环境,比如ESRI的ArcGIS地理信息系统。
PDB与其他调试工具不同,不是一个单独的程序,而是一个模块,可以将其导入到任何程序中,使用set_trace()函数在程序执行的任何时刻都可启动调试器。
- >>> import pdb
- >>> pdb.set_trace()
- --Return--
- > <stdin>(1)<module>()->None
- (Pdb)
在执行set_trace()之后,提示会从三箭头变成(pdb)——通过这个可以知道已经进入调试器。
首先通过输入help来查看调试器中可用的命令:
- (Pdb) help
-
- Documented commands (type help <topic>):
- ========================================
- EOF c d h list q rv undisplay
- a cl debug help ll quit s unt
- alias clear disable ignore longlist r source until
- args commands display interact n restart step up
- b condition down j next return tbreak w
- break cont enable jump p retval u whatis
- bt continue exit l pp run unalias where
-
- Miscellaneous help topics:
- ==========================
- exec pdb
获得命令的特定帮助:
- (Pdb) help continue
- c(ont(inue))
- Continue execution, only stop when a breakpoint is encountered.
命令名中的圆括号表示可以通过输入c、cont、或全部单词continue来激活continue命令。
本节来调试一个简单的函数。函数is_palindrome()可接收一个整数,并确定整数是否是回文。回文是正序和反序一样的序列。
创建palindrome.py:
- # palindrome.py
-
- import unittest
-
- def digits(x):
- """将整数转换为数字列表。
-
- Args:
- x:检查的数字
- Returns:数字列表,按照“x”的顺序排列。
- >>> digits(4586378)
- [4, 5, 8, 6, 3, 7, 8]
- """
-
- digs = []
- while x != 0:
- div, mod = divmod(x, 10)
- digs.append(mod)
- x = mod
- return digs
-
-
- def is_palindrome(x):
- """确定一个整数是否是回文。
-
- Args:
- x:需要进行回文检查的数字。
- Returns:如果数字“x”是回文数字就返回True,否则返回False。
- >>> is_palindrom(1234)
- False
- >>> is_palindrom(2468642)
- True
- """
-
- digs = digits(x)
- for f, r in zip(digs, reversed(digs)):
- if f != r:
- return False
- return True
-
-
- class Tests(unittest.TestCase):
- """''is_Palindrom()''函数的测试"""
- def test_negative(self):
- """检查结果为错误返回False。"""
- self.assertFalse(is_palindrome(1234))
-
- def test_positive(self):
- """检查结果为正确返回True。"""
- self.assertTrue(is_palindrome(1234321))
-
- def test_single_digit(self):
- """对于一位数字,检查能正常运行。"""
- for i in range(10):
- self.assertTrue(is_palindrome(i))
-
- if __name__ == '__main__':
- unittest.main()
上列代码有3个主要部分:
运行程序并发现错误,然后来看看如何使用PDB查找错误。
运行程序时,程序没有快速地运行完,在一直运行!很明显,程序出现了问题,先用Ctrl+C组合键来中止程序。
使用命令行调用在PDB的控制下启动程序:
- D:\python\写给程序员的Python教程\pyfund>python -m pdb palindrome.py
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(4)<module>()
- -> import unittest
- (Pdb)
上段代码使用-m参数告诉Python将特定的模块(在这个例子中是PDB)作为脚本,其余的参数会传递给该脚本。所以在这里,我们告诉Python将PDB模块作为一个脚本来执行,并且将文件的名称传递给它。
会看到PDB提示符。指向import unittest的箭头告诉我们,这是下一条要执行的语句。但是这条语句在哪里?
使用where命令来找到它:
- (Pdb) where
- c:\users\letu\appdata\local\programs\python\python311\lib\bdb.py(597)run()
- -> exec(cmd, globals, locals)
- <string>(1)<module>()
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(4)<module>()
- -> import unittest
where 命令报告了当前的调用堆栈,且最近的帧在底部。可以看到,PDB在palindrome.py的第一行暂停执行。这证实了之前讨论过的Python 执行的一个重要方面:所有事情都是在运行时进行求值的。此时,程序在导入语句之前暂停执行。
可以使用 next 命令运行到下一个语句来执行这个导入:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(6)<module>()
- -> def digits(x):
- (Pdb)
可以看到,程序会执行 digits()函数的def 调用。当再次执行 next 时,程序会转到is_palindrome()函数的定义:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(30)<module>()
- -> def is_palindrome(x):
- (Pdb)
你可能想知道为什么调试器没有进入 digits 函数体。毕竟,它没有像其他部分一样在运行时进行求值。答案是函数的主体只有在提供了参数的情况下才能被求值,所以只有在函数被调用时函数的主体才会运行。在导入时程序会检查函数体的语法是否正确,但是PDB 不允许我们调试语法检查的那一部分。
我们可以继续使用next 来完成程序的执行,但是由于不知道bug的位置,所以这可能不是一个非常有用的技术。请记住,上节程序的问题是,它似乎永远在运行。这听起来很像一个无限循环!
我们不是在单步执行代码,而是顺其自然地执行代码。当认为程序进入循环中时,就使用ctrl+C组合键来重新返回调试器:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(26)digits()
- -> x = mod
- (Pdb) --KeyboardInterrupt--
- (Pdb)
让程序运行几秒后,按 ctrl+C 组合键停止程序,此时程序显示,停在了palindrome.py的digits()函数中。如果想看具体某一行的源代码,可以使用PDB的list命令:
- (Pdb) list
- 21
- 22
- 23 while x != 0:
- 24 div, mod = divmod(x, 10)
- 25 digs.append(mod)
- 26 -> x = mod
- 27 return digs[::-1]
- 28
- 29
- 30 def is_palindrome(x):
- 31 """确定一个整数是否是回文。
- (Pdb)
可以看到,程序确实是一个循环,这证实了我们的怀疑,它可能涉及无限循环。
可以使用 return 命令来尝试运行到当前函数的结尾。如果不返回任何对象,那么就有非常有力的证据表明该程序是一个无限循环:
(Pdb) r
让它运行几秒来确认永远不会退出函数,然后按 Ctrl+C 组合键。一旦回到 PDB提示符,可以用quit命令退出PDB:
(Pdb) quit
由于我们知道问题在于digits(),因此可以使用前面提到的pdb.set trace()函数在digits()处设置一个显式的断点:
- def digits(x):
- """将整数转换为数字列表。
-
- Args:
- x:检查的数字
- Returns:数字列表,按照“x”的倒序排列。
- >>> digits(4586378)
- ['8', '7', '3', '6', '8', '5', '4']
- [4, 5, 8, 6, 3, 7, 8]
- """
-
- import pdb; pdb.set_trace()
-
- digs = []
-
-
- while x != 0:
- div, mod = divmod(x, 10)
- digs.append(mod)
- x = mod
- return digs[::-1]
请记住,set_trace()函数将停止执行并进入调试器。
现在可以在不指定PDB模块的情况下执行脚本:
- D:\python\写给程序员的Python教程\pyfund>python palindrome.py
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(20)digits()
- -> digs = []
- (Pdb)
可以看到,程序几乎立即进入了一个PDB提示符,并在digits()函数的开头停止执行。
为了验证当前所处的位置,使用where来看一下调用堆栈:
- (Pdb) where
- d:\python\写给程序员的python教程\pyfund\palindrome.py(66)<module>()
- -> unittest.main()
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\main.py(102)__init__()
- -> self.runTests()
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\main.py(274)runTests()
- -> self.result = testRunner.run(self.test)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\runner.py(217)run()
- -> test(result)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\suite.py(84)__call__()
- -> return self.run(*args, **kwds)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\suite.py(122)run()
- -> test(result)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\suite.py(84)__call__()
- -> return self.run(*args, **kwds)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\suite.py(122)run()
- -> test(result)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\case.py(678)__call__()
- -> return self.run(*args, **kwds)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\case.py(623)run()
- -> self._callTestMethod(testMethod)
- c:\users\letu\appdata\local\programs\python\python311\lib\unittest\case.py(579)_callTestMethod()
- -> if method() is not None:
- d:\python\写给程序员的python教程\pyfund\palindrome.py(54)test_negative()
- -> self.assertFalse(is_palindrome(1234))
- d:\python\写给程序员的python教程\pyfund\palindrome.py(43)is_palindrome()
- -> digs = digits(x)
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(20)digits()
- -> digs = []
- (Pdb)
请注意,最近的帧在这个堆栈的末尾。经过大量的单元测试函数之后,我们看到它确实在digits()函数中,并且被is_palindrome()调用,就像我们所期望的那样。
本节我们要继续观察执行,看看为什么程序不退出这个函数的循环。使用next移动到循环体的第一行:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(23)digits()
- -> while x != 0:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(24)digits()
- -> div, mod = divmod(x, 10)
- (Pdb)
现在来看一些变量的值。并验证是否发生我们期望发生的事情。可以使用print命令来检查值:
- (Pdb) print(digs)
- []
- (Pdb) print(x)
- 1234
- (Pdb)
这看起来是正确的。digs列表(保存数字序列)是空的,x是我们传入的。我们期望divmod()函数返回123和4:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(25)digits()
- -> digs.append(mod)
- (Pdb) print (div,mod)
- 123 4
- (Pdb)
这看起来是正确的:divmod()函数已经从数字中删除了最低有效位,下一行代码会把这个数字放到结果列表中:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(26)digits()
- -> x = mod
查看digs,可以看到它现在包含了mod:
- (Pdb) print(digs)
- [4]
- (Pdb)
下一行代码将更新x,这样就可以继续剪切digits了:
- (Pdb) next
- > d:\python\写给程序员的python教程\pyfund\palindrome.py(23)digits()
- -> while x != 0:
- (Pdb)
可以看到,执行回到了我们期望的while循环。接下来查看x以确保它的数值正确:
- (Pdb) print(x)
- 4
- (Pdb)
等一下!我们期望x保存不在结果列表中的数字。但相反,它只保存了结果列表中的数字。显然,我们在更新x时犯了错误!
如果看看代码,我们很快就会明白应该把div而不是mod分配给x。退出PDB:
(Pdb) quit
请注意,由于PDB和unittest相互作用,你可能必须执行几次quit才可以退出。
在退出PDB之后,删除set_trace()调用并修改digits()来解决发现的问题:
- def digits(x):
- """将整数转换为数字列表。
-
- Args:
- x:检查的数字
- Returns:数字列表,按照“x”的倒序排列。
- >>> digits(4586378)
- ['8', '7', '3', '6', '8', '5', '4']
- [4, 5, 8, 6, 3, 7, 8]
- """
-
- digs = []
-
-
- while x != 0:
- div, mod = divmod(x, 10)
- digs.append(mod)
- x = div
- return digs[::-1]
如果现在运行这个程序,就会看到它通过了所有的测试,而且运行速度非常快:
-
- D:\python\写给程序员的Python教程\pyfund>python palindrome.py
- ...
- ----------------------------------------------------------------------
- Ran 3 tests in 0.000s
-
- OK
上列代码是一个基本的PDB会话,它演示了PDB的一些核心功能。PDB有许多其他的命令和功能,但是学习它们的最好方法是开始使用PDB并尝试使用命令。这个回文项目可以作为学习PDB大部分功能的一个很好的例子。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。