求你了,别再用 print 调试代码了

大家好,我是小五。

对于每个程序开发者来说,调试几乎是必备技能。代码写到一半卡住了,不知道这个函数执行完的返回结果是怎样的?调试一下看看代码运行到一半报错了,什么情况?怎么跟预期的不一样?调试一下看看调试的方法多种多样,不同的调试方法适合不同的场景和人群。

  • 如果你是刚接触编程的小萌新,对很多工具的使用还不是很熟练,那么 print 和 log 大法好

  • 如果你在本地(Win或者Mac)电脑上开发,那么 IDE 的图形化界面调试无疑是最适合的;

  • 如果你在服务器上排查BUG,那么使用 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) 的详细使用手册,是不是觉得还不错?

如果你还有其他关于调试的技巧,可以留言区分享出来,一起学习一下~

(0)

相关推荐

  • 反射,双下方法

    一. 反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被程序 ...

  • 【Python核心编程笔记】一、Python中一切皆对象

    Python中一切皆对象 本章节首先对比静态语言以及动态语言,然后介绍 python 中最底层也是面向对象最重要的几个概念-object.type和class之间的关系,以此来引出在python如何做 ...

  • 11个案例讲透 Python 函数参数

    接下来是正文. 1. 参数分类 函数,在定义的时候,可以有参数的,也可以没有参数. 从函数定义的角度来看,参数可以分为两种: 必选参数:调用函数时必须要指定的参数,在定义时没有等号 可选参数:也叫默认 ...

  • Python 中的函数装饰器和闭包

    函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念. 装饰器的基本概念 装饰器是一个可调用对象,它的参数是另一个函数,称为被装饰函数.装饰器可以修改这个函数再将其返回, ...

  • python测试开发django-99.views视图中 locals() 函数使用

    前言 locals() 函数是python内置函数,会以字典类型返回当前位置的全部局部变量. Python locals() 函数 locals() 函数语法:locals() 参数无,返回字典类型的 ...

  • 温故而知新--day2

    温故而知新--day2 类 类与对象 类是一个抽象的概念,是指对现实生活中一类具有共同特征的事物的抽象.其实列化后称为对象.类里面由类属性组成,类属性可以分为数据属性和函数属性(函数属性又称为类方法) ...

  • 调试Python代码,千万别再用Print了!

    Python客栈 2月18日 以下文章来源于法纳斯特 ,作者小F 法纳斯特分享学习Python爬虫.数据分析.数据挖掘的点滴. Hello 大家好- 相信大部分人学习Python,肯定会用print( ...

  • 调试 Python 代码,建议不要再用 Print 了!

    大家好,我是安果! 相信大部分人学习 Python,肯定会用 print() 这个内置函数,来调试代码的 那么在一个大型的项目中,如果你也是使用 print 来调试你的 Python 代码,你就会发现 ...

  • 数学的威力,原则上是先求保命,再去干掉对手......

    五个囚犯先后从100颗绿豆中抓绿豆,抓得最多和最少的人将被处死,不能交流,可以摸出剩下绿豆的数量,谁的存活几率最大? 提示:1.他们都是很聪明的人:           2.他们的原则是先求保命,再去 ...

  • 先求幸福人生,再求意义人生!

    [马列]穷和懒之间到底是什么样的关系?用唯物主义辩证法分析:颜值即正义?[马列]我们应该用何种态度与弱者相处?爱情是唯物的还是唯心的?[辩证思维]看杠精鼻祖惠子如何偷换辩题.关于用马克思主义治疗抑郁症 ...

  • 求你打招呼别再用“How are you”了行不!

    求你打招呼别再用“How are you”了行不!

  • 流血流汗的商家喊话王兴:求美团点评别再涨佣金了,我们快活不下去了!

    受疫情影响,餐饮行业遭遇到百年不遇之危机.相关数据表明,仅仅春节七天,餐饮行业零售损失就超过5000亿元.外卖几乎成为中小店家唯一可以维持生计的出路.在此情况下,美团外卖平台却被曝出提高佣金问题,遭遇 ...

  • 【冬至大如年】今日不求福,错过再等一年!最好的祈福消业时机,只需一分钟,助全家富贵好运一整年!

    冬至已至,天寒地冻,2020年又走到了尾声. 在今年这个特别的年份,防疫的常态化和气候异常造成的灾情,让不少偏远地区中小寺院自养艰难,道粮不继,甚至日常基本生活都无法持续保障. 因此,我们于11月初发 ...

  • 求向佐郭碧婷别再上节目了!这是要把恋爱结婚生子直播到底吗?

    8号风曝 有趣/有撩/有聊/有料 怕了怕了,真的怕了. 还记得之前热搜被奚梦瑶和何猷君支配的恐惧吗?→简直是住在了热搜榜上!奚梦瑶何猷君就不能安静过日子吗? 如今,能与他们匹敌的来了... 郭碧婷和向 ...

  • 【黄道十二宫·水瓶座】求你,不要再为难我的爱人了

    欢迎来到[希腊神话] 点击"襄子讲西方"关注我们哟 这是[十二星座]第 02 篇文章 水瓶座:求你,不要在为难我的爱人了 最近,宙斯很失落,因为他和赫拉的女儿赫柏出嫁了,嫁给了刚刚 ...