还用print在Python中debug吗?试试PySnooper吧!

print对很多人来说算是最常用的debug神器了,只需要在合适的地方插入print打印变量的值,就能判断代码在这个地方是不是还按照你的预期在运行。不过这也带来一些麻烦:首先需要找准位置,然后写出对应的print语句。当函数复杂、变量多的时候确实挺烦的,通常需要多个地方插入多个变量的print

最近一个刚刚上线的Python包“PySnooper”更优雅地解决了这一需求——只需要对关注的函数前加上一个装饰器@pysnooper.snoop(),就可以将这个函数运行的时间,行号、运行过程中变量的数值以及代码等内容输出到stderr。项目刚在GitHub上线1天左右,已经有超过1300个star,可以说是相当火爆了(更新:该项目成为2019.04.23 GitHub每日趋势榜第一名):

https://github.com/cool-RR/PySnooper

使用方法

安装很简单,直接使用pip就行:

$ pip install pysnooper

使用也依然简单,我们这里写个简单的运算斐波那契数列的函数进行测试:

from __future__ import print_function
import pysnooper

@pysnooper.snoop()
def fib(n):
    a, b = 0, 1
    for i in xrange(n+1):
        a, b = b, a + b
        return a

def main():
    for n in xrange(3):
       print(fib(n), end=' ')

if __name__ == '__main__':
    main()

运行程序就可以看到PySnooper的输出结果:

$ python mytest.py 
Starting var:.. n = 0
00:10:16.244816 call         4 @pysnooper.snoop()
00:10:16.245712 line         6     a, b = 0, 1
New var:....... a = 0
New var:....... b = 1
00:10:16.245835 line         7     for i in xrange(n+1):
New var:....... i = 0
00:10:16.245926 line         8         a, b = b, a + b
Modified var:.. a = 1
00:10:16.245998 line         7     for i in xrange(n+1):
00:10:16.246048 line         9     return a
00:10:16.246115 return       9     return a
Starting var:.. n = 1
00:10:16.246228 call         4 @pysnooper.snoop()
00:10:16.246278 line         6     a, b = 0, 1
New var:....... a = 0
New var:....... b = 1
00:10:16.246358 line         7     for i in xrange(n+1):
New var:....... i = 0
00:10:16.246425 line         8         a, b = b, a + b
Modified var:.. a = 1
00:10:16.246491 line         7     for i in xrange(n+1):
Modified var:.. i = 1
00:10:16.246556 line         8         a, b = b, a + b
Modified var:.. b = 2
00:10:16.246620 line         7     for i in xrange(n+1):
00:10:16.246670 line         9     return a
00:10:16.246720 return       9     return a
Starting var:.. n = 2
00:10:16.246815 call         4 @pysnooper.snoop()
00:10:16.246865 line         6     a, b = 0, 1
New var:....... a = 0
New var:....... b = 1
00:10:16.246944 line         7     for i in xrange(n+1):
New var:....... i = 0
00:10:16.247012 line         8         a, b = b, a + b
Modified var:.. a = 1
00:10:16.247076 line         7     for i in xrange(n+1):
Modified var:.. i = 1
00:10:16.247140 line         8         a, b = b, a + b
Modified var:.. b = 2
00:10:16.247204 line         7     for i in xrange(n+1):
Modified var:.. i = 2
00:10:16.247268 line         8         a, b = b, a + b
Modified var:.. a = 2
Modified var:.. b = 3
00:10:16.247356 line         7     for i in xrange(n+1):
00:10:16.247404 line         9     return a
00:10:16.247453 return       9     return a
1 1 2 

可以看到PySnooper输出了函数每条语句的运行时间(方便做性能优化测试)、变量的初始化赋值和修改后的值、行号、代码都进行了输出。最后输出的则是程序运行的结果。

自定义参数

PySnooper会输出到stderr。如果想要输出到文件,可以在命令行使用2> /my/log/file.log重定向到文件。也可以给装饰器增加一个参数:

@pysnooper.snoop('/my/log/file.log')

如果想检查一些非局部变量,也可以使用参数variables

@pysnooper.snoop(variables=('foo.bar', 'self.whatever'))

通过参数depth设定调用函数的深度:

@pysnooper.snoop(depth=2)

如果stderr和stdout一起输出可能会看不太清楚,可以通过prefix参数给PySnooper的输出结果添加一个前缀,比如:

@pysnooper.snoop(prefix='ZZZ ')

一些不足

稍微做了一些测试之后也发现了不足:

  • 即使加了prefix,输出看着还是有点乱。如果看着太烦,我可能还是宁愿用print。可以类似IPython对输出内容用不同的颜色显示会更好。
  • 只能针对return返回的函数使用,如果是使用yield的生成器,似乎无法正常工作。
  • depth参数只能设定调用其他函数的深度,但是无法设置递归函数的深度(调用自己),并且设定depth超过1,还会引发报错(在调用其他函数时depth设定超过实际深度并不会报错)。

后记

后面这两点不足我在GitHub上提交了两个issues,作者表示欢迎pull requests。然而时间关系,只能等待有志之士来填坑了:

标签: Python, 编程

知识共享许可协议 作者: 链接:https://byteofbio.com/archives/5.html
本文采用“署名-非商业性使用-相同方式分享 4.0 国际许可协议”进行许可

暂无评论

添加新评论