Python: 告别Print?

python debug神器来了

Posted by 肖哥shelwin on June 1, 2019

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无需进行繁琐的配置,更加简单。

直接看下面这个例子吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

执行这段代码,输出结果(节选)如下:

1
2
3
4
5
6
7
8
9
10
11
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支持灵活多样的程序调试,包括但不限于:

  1. 给函数添加装饰器@pysnooper.snoop(),完成对函数的监听。

  2. 使用with pysnooper.snoop()语句,实现对程序块(block),即一行或者多行程序进行监听。

  3. 使用@pysnooper.snoop(‘/my/log/file.log’),将监听结果重定向到文件系统。

  4. 监听非局部变量的值:
    1
    
     @pysnooper.snoop(variables=('foo.bar','self.whatever'))
    
  5. 监听一个列表或者字典变量的所有元素或者属性:
    1
    
     @pysnooper.snoop(watch=('foo.bar','self.x["whatever"]'))
    
  6. 深度监听——监听函数中的行所调用的其他函数:
    1
    
     @pysnooper.snoop(depth=2)
    
  7. 在多线程程序中,指定监听哪些线程:
    1
    
     @pysnooper.snoop(thread_info=True)
    

    PySnooper的更多高级用法参见:https://github.com/cool-RR/PySnooper#advanced-usage。

另外,PySnooper的安装十分简单:

1
pip install pysnooper

总结一下,PySnooper是一个使用简单,功能强大,效率高的Python调试工具,聚集各种优点于一身,难怪这么快就受到了社区的热烈欢迎。

我是肖哥shelwin,一个高质量软件工程实践者和推动者。欢迎扫描下方二维码,添加我的个人公众号测试不将就,获得更多自动化测试, 持续集成, 软件工程实践, Python编程等领域原创文章。

公众号