点击蓝色“Python空间”关注我丫python
加个“星标”,天天一块儿快乐的学习web

对于每一个程序开发者来讲,调试几乎是必备技能。编程
代码写到一半卡住了,不知道这个函数执行完的返回结果是怎样的?调试一下看看服务器
代码运行到一半报错了,什么状况?怎么跟预期的不同?调试一下看看微信
调试的方法多种多样,不一样的调试方法适合不一样的场景和人群。多线程
若是你是刚接触编程的小萌新,对不少工具的使用还不是很熟练,那么 print 和 log 大法好app
若是你在本地(Win或者Mac)电脑上开发,那么 IDE 的图形化界面调试无疑是最适合的;编辑器
若是你在服务器上排查BUG,那么使用 PDB 进行无图形界面的调试应该是首选,详情请戳明哥以前的文章:让代码调试再也不难 - pdb函数
若是你要在本地进行开发,可是项目的进行须要依赖复杂的服务器环境,那么能够了解下 PyCharm 的远程调试,详情请戳明哥以前的文章:不能不会的远程调试技巧工具
除了以上,今天明哥再给你介绍一款很是好用的调试工具,它能在一些场景下,大幅度提升调试的效率, 那就是 PySnooper
,它在 Github 上已经收到了 13k 的 star,得到你们的一致好评。
有了这个工具后,就算是小萌新也能够直接无门槛上手,今后与 print 说再见~
1. 快速安装
执行下面这些命令进行安装 PySnooper
$ python3 -m pip install pysnooper
# 或者
$ conda install -c conda-forge pysnooper
# 或者
$ yay -S python-pysnooper
2. 简单案例
下面这段代码,定义了一个 demo_func 的函数,在里面生成一个 profile 的字典变量,而后去更新它,最后返回。
代码自己没有什么实际意义,可是用来演示 PySnooper 已经足够。
import pysnooper
@pysnooper.snoop()
def demo_func():
profile = {}
profile["name"] = "写代码的明哥"
profile["age"] = 27
profile["gender"] = "male"
return profile
def main():
profile = demo_func()
main()
如今我使用终端命令行的方式来运行它
[root@iswbm ~]# python3 demo.py
Source path:... demo.py
17:52:49.624943 call 4 def demo_func():
17:52:49.625124 line 5 profile = {}
New var:....... profile = {}
17:52:49.625156 line 6 profile["name"] = "写代码的明哥"
Modified var:.. profile = {'name': '写代码的明哥'}
17:52:49.625207 line 7 profile["age"] = 27
Modified var:.. profile = {'name': '写代码的明哥', 'age': 27}
17:52:49.625254 line 8 profile["gender"] = "male"
Modified var:.. profile = {'name': '写代码的明哥', 'age': 27, 'gender': 'male'}
17:52:49.625306 line 10 return profile
17:52:49.625344 return 10 return profile
Return value:.. {'name': '写代码的明哥', 'age': 27, 'gender': 'male'}
Elapsed time: 00:00:00.000486
能够看到 PySnooper 把函数运行的过程所有记录了下来,包括:
代码的片断、行号等信息,以及每一行代码是什么时候调用的?
函数内局部变量的值如何变化的?什么时候新增了变量,什么时候修改了变量。
函数的返回值是什么?
运行函数消耗了多少时间?
而做为开发者,要获得这些如此详细的调试信息,你须要作的很是简单,只要给你想要调试的函数上带上一顶帽子(装饰器) -- @pysnooper.snoop()
便可。
3. 详细使用
2.1 重定向到日志文件
@pysnooper.snoop()
不加任何参数时,会默认将调试的信息输出到标准输出。
对于单次调试就能解决的 BUG ,这样没有什么问题,可是有一些 BUG 只有在特定的场景下才会出现,须要你把程序放在后面跑个一段时间才能复现。
这种状况下,你能够将调试信息重定向输出到某一日志文件中,方便追溯排查。
@pysnooper.snoop(output='/var/log/debug.log')
def demo_func():
...
2.2 跟踪非局部变量值
PySnooper 是以函数为单位进行调试的,它默认只会跟踪函数体内的局部变量,若想跟踪全局变量,能够给 pysnooper.snoop()
加上 watch
参数
out = {"foo": "bar"}
@pysnooper.snoop(watch=('out["foo"]'))
def demo_func():
...
如此一来,PySnooper 会在 out["foo"]
值有变化时,也将其打印出来

watch 参数,接收一个可迭代对象(能够是list 或者 tuple),里面的元素为字符串表达式,什么意思呢?看下面例子就知道了
@pysnooper.snoop(watch=('out["foo"]', 'foo.bar', 'self.foo["bar"]'))
def demo_func():
...
和 watch
相对的,pysnooper.snoop()
还能够接收一个函数 watch_explode
,表示除了这几个参数外的其余全部全局变量都监控。
@pysnooper.snoop(watch_explode=('foo', 'bar'))
def demo_func():
...
2.3 设置跟踪函数的深度
当你使用 PySnooper 调试某个函数时,若该函数中还调用了其余函数,PySnooper 是不会傻傻的跟踪进去的。
若是你想继续跟踪该函数中调用的其余函数,能够经过指定 depth
参数来设置跟踪深度(不指定的话默认为 1)。
@pysnooper.snoop(depth=2)
def demo_func():
...
2.4 设置调试日志的前缀
当你在使用 PySnooper 跟踪多个函数时,调试的日志会显得杂乱无章,不方便查看。
在这种状况下,PySnooper 提供了一个参数,方便你为不一样的函数设置不一样的标志,方便你在查看日志时进行区分。
@pysnooper.snoop(output="/var/log/debug.log", prefix="demo_func: ")
def demo_func():
...
效果以下

2.5 设置最大的输出长度
默认状况下,PySnooper 输出的变量和异常信息,若是超过 100 个字符,被会截断为 100 个字符。
固然你也能够经过指定参数 进行修改
@pysnooper.snoop(max_variable_length=200)
def demo_func():
...
您也可使用max_variable_length=None它从不截断它们。
@pysnooper.snoop(max_variable_length=None)
def demo_func():
...
2.6 支持多线程调试模式
PySnooper 一样支持多线程的调试,经过设置参数 thread_info=True
,它就会在日志中打印出是在哪一个线程对变量进行的修改。
@pysnooper.snoop(thread_info=True)
def demo_func():
...
效果以下

2.7 自定义对象的格式输出
pysnooper.snoop()
函数有一个参数是 custom_repr
,它接收一个元组对象。
在这个元组里,你能够指定特定类型的对象以特定格式进行输出。
这边我举个例子。
假如我要跟踪 person 这个 Person 类型的对象,因为它不是常规的 Python 基础类型,PySnooper 是没法正常输出它的信息的。
所以我在 pysnooper.snoop()
函数中设置了 custom_repr
参数,该参数的第一个元素为 Person,第二个元素为 print_persion_obj
函数。
PySnooper 在打印对象的调试信息时,会逐个判断它是不是 Person 类型的对象,如果,就将该对象传入 print_persion_obj
函数中,由该函数来决定如何显示这个对象的信息。
class Person:pass
def print_person_obj(obj):
return f"<Person {obj.name} {obj.age} {obj.gender}>"
@pysnooper.snoop(custom_repr=(Person, print_person_obj))
def demo_func():
...
完整的代码以下
import pysnooper
class Person:pass
def print_person_obj(obj):
return f"<Person {obj.name} {obj.age} {obj.gender}>"
@pysnooper.snoop(custom_repr=(Person, print_person_obj))
def demo_func():
person = Person()
person.name = "写代码的明哥"
person.age = 27
person.gender = "male"
return person
def main():
profile = demo_func()
main()
运行一下,观察一下效果。

若是你要自定义格式输出的有不少个类型,那么 custom_repr
参数的值能够这么写
@pysnooper.snoop(custom_repr=((Person, print_person_obj), (numpy.ndarray, print_ndarray)))
def demo_func():
...
还有一点我提醒一下,元组的第一个元素能够是类型(如类名Person 或者其余基础类型 list等),也能够是一个判断对象类型的函数。
也就是说,下面三种写法是等价的。
# 【第一种写法】
@pysnooper.snoop(custom_repr=(Person, print_persion_obj))
def demo_func():
...
# 【第二种写法】
def is_persion_obj(obj):
return isinstance(obj, Person)
@pysnooper.snoop(custom_repr=(is_persion_obj, print_persion_obj))
def demo_func():
...
# 【第三种写法】
@pysnooper.snoop(custom_repr=(lambda obj: isinstance(obj, Person), print_persion_obj))
def demo_func():
...
以上就是明哥今天给你们介绍的一款调试神器(PySnooper
) 的详细使用手册,是否是以为还不错?
若是你还有其余关于调试的技巧,能够留言区分享出来,一块儿学习一下~

我将本身的原创文章整理成了一本电子书,取名《Python修炼之道》,一共 400 页,17w+ 字,目录以下:
如今免费送给你们,在个人公众号后台回复 修炼之道 便可获取。
最后我最近建了一个读者交流群,想加入的能够在公众号后台回复「加群」便可~
👆扫描上方二维码便可关注
本文分享自微信公众号 - Python空间(Devtogether)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。