调试是开发过程当中不可避免的一个环节,在Python中咱们使用print、logging、assert等方法进行调试既简单又实用,但毕竟有其局限性。今天这篇文章为你们带来三个工具,其中有Python的内置模块也有第三方库,它们提供了调试代码所需的大部分经常使用功能,将极大的提高咱们的开发和bug排除效率。python
1.PDBgit
pdb是Python中的一个内置模块,启用pdb后能够对代码进行断点设置和跟踪调试。为了演示方便,咱们准备一个样例程序pdb_test.py:github
def countnumber(number): for i in range(number): print(i) if __name__ == '__main__': countnumber(10)
以后在终端中输入python -m pdb pdb_test.py命令,进入pdb的调试模式:
这时咱们就能够经过各类命令控制代码执行或者查看当前变量,例如l能够查看全部代码,n是执行下一步代码,p能够查看当前变量等等,须要注意的是命令n只会执行主程序中的代码,若是想要单步执行子函数中的代码,须要使用s指令,调试效果以下:
这时咱们就能够经过各类命令控制代码执行或者查看当前变量,例如l能够查看全部代码,n是执行下一步代码,p能够查看当前变量等等,须要注意的是命令n只会执行主程序中的代码,若是想要单步执行子函数中的代码,须要使用s指令,调试效果以下:
能够看到,经过s指令(若是只想在主函数中单步执行可使用n)和p指令,咱们控制程序单步运行并实时查看了相关变量。可是单步执行毕竟是一种效率很是低下的调试方式,尤为当代码量比较大的时候更是噩梦,这时就须要用到pdb的set_trace()方法,咱们对样例程序pdb_test.py作一点修改:django
''' 遇到问题没人解答?小编建立了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴, 互帮互助,群里还有不错的视频学习教程和PDF电子书! ''' import pdb def countnumber(number): for i in range(number): print(i) pdb.set_trace() if __name__ == '__main__': countnumber(10)
pdb.set_trace()的做用就是在代码中设置断点,在pdb调试模式下,使用c命令就会直接跳转到下一个断点位置,若是以后没有其余断点就会执行彻底部代码,调试效果以下:
除了上面提到的几个指令之外,pdb还有其余一些比较经常使用的命令(见下表),综合使用基本可以知足平常的调试需求。
2.Better-exceptionswindows
better-exceptions是一个Python第三方库,做者对他的定义是“使异常信息更加美观和详尽”。在正式使用以前先说下这个库的安装:多线程
第一步,使用pip install better_exceptions安装better-exceptions库;
第二步,使用export BETTER_EXCEPTIONS=1(Linux / OSX)或setx BETTER_EXCEPTIONS 1(Windows)设置环境变量。app
如今就能够正常使用better-exceptions进行调试了,为了演示效果更加明显,咱们对上文中的代码稍做修改做为本次的样例程序better_test.py:dom
def divisionnumber(number, div): for i in range(div): print(number / i) if __name__ == '__main__': divisionnumber(10, 10)
很明显,上面这段代码在执行过程当中会由于分母为0而抛出异常,如今咱们执行python better_test.py,看看启用了better-exceptions后的异常信息是什么样子的:
从上面这幅图能够看出better-exceptions对异常信息的修改主要体如今两个方面:ide
一是对产生异常的代码进行了颜色标注;函数
二是对产生异常的代码中的相关变量值进行了输出(包括函数等对象);
这样一来,不少时候咱们只须要根据better-exceptions输出的辅助信息就能判断产生异常的位置和缘由,而没必要像之前同样再次查看源代码并观察运行结果,正如做者所说:Pretty and more helpful。
可是,过多的信息输出也会有问题,那就是当代码层级结构比较复杂的时候,better-exceptions输出的辅助信息可能会很是之多,就好比上面的divisionnumber函数,他所在的地址信息多数时候咱们并不关心,为了屏蔽这些“垃圾”信息,咱们能够在代码中加一行:
better_exceptions.MAX_LENGTH = XXX
XXX是容许显示的最大字符长度,好比这里设置为10,再来运行better_test.py这个程序就会是下面的结果:
能够看到,对函数divisionnumber的注释只显示了最开始的"<function"这几个字符。
除了上面提到的功能以外,better-exceptions还能够和logging还有django无缝接入,这使得它的应用更加灵活,关于这方面内容你们能够查看项目文档。
还有一点须要提醒你们,若是你是在windows下使用,可能会出现下图中的乱码问题,这是因为better-exceptions的内设编码格式所致使的。
解决的办法是在安装后,对better_exceptions目录下的encoding.py文件第10行代码进行以下修改:
# 原代码: ENCODING = locale.getpreferredencoding() # 修改成: ENCODING = 'utf-8'
3.PySnooper
PySnooper也是一个Python的第三方库,他的特色是可以精准的显示每条代码的执行顺序、执行时间以及随之带来的局部变量的改变等等。值得一提的是,做为一个发布不满半年的库,PySnooper在github上已经达到了1.2W星,其受欢迎程度可见一斑。
PySnooper的使用能够说是很是的方便,直接在代码中以装饰器的形式调用就能够了。固然在引用前你得使用pip install pysnooper或者conda install -c conda-forge pysnooper安装这个库。咱们仍是举一个例子来进行演示,样例代码以下:
''' 遇到问题没人解答?小编建立了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴, 互帮互助,群里还有不错的视频学习教程和PDF电子书! ''' import pysnooper import random @pysnooper.snoop() def foo(): lst = [] for i in range(10): lst.append(random.randrange(1, 1000)) lower = min(lst) upper = max(lst) mid = (lower + upper) / 2 print(lower, mid, upper) foo()
在上面这段代码中,咱们先是生成10个1到1000之间的随机数,而后计算他们之中的最大最小值和中位数,惟一的不一样在于第三行多了一条语句@pysnooper.snoop(),咱们运行如下代码,发现除了正常的print结果以外,多了许多内容(内容太多,下面只显示一部分):
19:51:57.704857 call 16 def foo(): 19:51:57.705860 line 17 lst = [] New var:....... lst = [] 19:51:57.705860 line 18 for i in range(10): New var:....... i = 0 19:51:57.705860 line 19 lst.append(random.randrange(1, 1000)) Modified var:.. lst = [758] 19:51:57.705860 line 18 for i in range(10): Modified var:.. i = 1 .................... 19:51:57.706818 line 22 upper = max(lst) New var:....... upper = 927 19:51:57.706818 line 23 mid = (lower + upper) / 2 New var:....... mid = 552.0 19:51:57.706818 line 24 print(lower, mid, upper) 19:51:57.706818 return 24 print(lower, mid, upper) Return value:.. None
这都是PySnooper跟踪监控的结果,正如上面所说,他准确记录的每条代码的运行时间、顺序以及相关的变量值。
做为一个星标1.2W+的项目,PySnooper的功能确定不会这么简单,@pysnooper.snoop()中是能够接收参数的,好比咱们以为输出内容太多,能够考虑把信息记录到log日志中,这个功能只须要加一个log文件定位参数就能搞定:
@pysnooper.snoop('file.log')
@pysnooper.snoop()支持的参数还有不少,分别对应了不一样的功能,例如监控自定义表达式、监控底层函数、支持多线程等等,详见项目文档。
此外,pysnooper还支持局部监控,通常来讲咱们写的代码都比较长,而须要监控的只是其中的一小部分,这时候就能够把须要监控的代码放到一个block里。咱们修改下刚才的代码,只对计算最大最小值和中位数的部分进行监控,修改后的代码以下:
''' 遇到问题没人解答?小编建立了一个Python学习交流QQ群:857662006 寻找有志同道合的小伙伴, 互帮互助,群里还有不错的视频学习教程和PDF电子书! ''' import pysnooper import random def foo(): lst = [] for i in range(10): lst.append(random.randrange(1, 1000)) with pysnooper.snoop(): lower = min(lst) upper = max(lst) mid = (lower + upper) / 2 print(lower, mid, upper) foo()
运行以后发现监控信息精简了不少:
New var:....... lst = [562, 341, 552, 353, 628, 302, 430, 188, 955, 108] New var:....... i = 9 20:02:47.359272 line 21 lower = min(lst) New var:....... lower = 108 20:02:47.359272 line 22 upper = max(lst) New var:....... upper = 955 20:02:47.360269 line 23 mid = (lower + upper) / 2
使用with pysnooper.snoop()模式依然保留了对各类参数的支持,我的认为这种模式更加符合实践需求。
小结:今天介绍了三个不借助IDE就能方便使用的调试工具,三个工具的调试思路和适用场景也各不相同,你们能够根据须要灵活选用。不过话说回来,我我的最喜欢的仍是PySnooper,你最喜欢哪一款呢?