Print 也许是 Python 中使用频率最高的一个函数。很多小白都是从 Hello World 程序开始认识 Python,而 Python 的 Hello World 程序只有一行,那就是调用内置的 Print 函数,向控制台输出字符串 “Hello World”。
不仅小白,哪怕是 Python 开发者,通常也是 Print 函数的重度用户。Print 最大的应用场景,便是用于调试 Python 程序。比如,用 Print 打印程序的运行步骤,或用 Print 打印程序运行过程中某个变量的值的变化情况。由于 Python 是无须编译就能执行的解释型编程语言,因此加上 Print 调试语句后,开发者能够立即执行 Python 程序,得到调试结果。
使用 Print 进行调试的好处是非常直白,易于上手,但是弊端也是明显的:如果要打印的信息比较多,就需要写很多行的 Print 语句;在调试结束后,往往还需要逐一删除这些语句。这是一个繁琐的过程。一言以蔽之,使用 Print 调试的缺点是效率较低。
除了 Print 函数,另一种常用的 Python 程序调试工具是 Python 的日志模块(logging)。Logging 模块根据预定义的格式,将需要的信息写入日志文件。通过跟踪日志文件,也可以实现调试的目的。
使用 logging 进行调试,看起来比 Print 函数更规范一些。并且,当调试结束后,logging 语句无须删除,在产品环境中依然可以发挥作用。另外,logging 模块还支持我们通过调整 log 等级 (INFO/DEBUG/WARN/ERR) 来动态决定打印何种日志。美中不足的是,logging 模块在使用之前需要进行比较繁琐的配置 (Setup),因此使用难度稍大,不够方便。
那么,有没有更好的调试办法呢?最近,Github 上出现了一个新的专门用于调试 Python 程序的第三方库,名叫PySnooper。Snooper 在英文中是监听器的意思。PySnooper,顾名思义,就是监听 Python 程序执行过程的工具。PySnooper 一经问世,便引起 Python 社区的严重关注。仅仅一个月时间便收获了 10K+ 个 STAR,着实十分火爆。
相比 Print 调试往往需要写很多行 Print 语句,使用 PySnooper 仅仅一行代码就能实现对整个函数的调试,更加高效;相比 Logging 模块,使用 PySnooper 无需进行繁琐的配置,更加简单。
直接看下面这个例子吧。
import pysnooper
@pysnooper.snoop()
def number_to_bits(number):
if number:
bits = []
while number:
number, remainder = divmod(number, 2)
bits.insert(0, remainder)
return bits
else:
return [0]
number_to_bits(6)
执行这段代码,输出结果 (节选) 如下:
Starting var:.. number = 6
15:29:11.327032 call 4 def number_to_bits(number):
15:29:11.327032 line 5 if number:
15:29:11.327032 line 6 bits = []
New var:....... bits = []
15:29:11.327032 line 7 while number:
15:29:11.327032 line 8 number, remainder = divmod(number, 2)
New var:....... remainder = 0
Modified var:.. number = 3
15:29:11.327032 line 9 bits.insert(0, remainder)
Modified var:.. bits = [0]
可见,只需要导入 PySnooper 模块,并且给函数加上装饰器@pysnooper.snoop(),我们就可以实现对一个 Python 函数的监听 (调试)。
在上面这个例子中,根据输出结果,我们可以得到:
程序执行步骤的顺序,比如执行结果的第 2 行告诉我们,在 15:29:11.327032 这一时刻执行了 def number_to_bits 这一行代码。
程序中变量的值的变化情况,比如执行结果的第 9 行告诉我们,局部变量 number 此时的值发生了变化,变成了 3。
PySnooper 支持灵活多样的程序调试,包括但不限于:
给函数添加装饰器@pysnooper.snoop(),完成对函数的监听。
使用 with pysnooper.snoop() 语句,实现对程序块(block),即一行或者多行程序进行监听。
使用@pysnooper.snoop('/my/log/file.log'),将监听结果重定向到文件系统。
监听非局部变量的值:
@pysnooper.snoop(variables=('foo.bar','self.whatever'))
监听一个列表或者字典变量的所有元素或者属性:
@pysnooper.snoop(watch=('foo.bar','self.x["whatever"]'))
深度监听——监听函数中的行所调用的其他函数:
@pysnooper.snoop(depth=2)
在多线程程序中,指定监听哪些线程:
@pysnooper.snoop(thread_info=True)
PySnooper 的更多高级用法参见:https://github.com/cool-RR/PySnooper#advanced-usage。
另外,PySnooper 的安装十分简单:
pip install pysnooper
总结一下,PySnooper 是一个使用简单,功能强大,效率高的 Python 调试工具,聚集各种优点于一身,难怪这么快就受到了社区的热烈欢迎。
最后,欢迎关注我的个人微信公众号《测试不将就》,我们一起探讨高质量软件养成之道。