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

Karen110 等级 493 0 0

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

大家好,我是明哥。

对于每个程序开发者来说,调试几乎是必备技能。

代码写到一半卡住了,不知道这个函数执行完的返回结果是怎样的?调试一下看看

代码运行到一半报错了,什么情况?怎么跟预期的不一样?调试一下看看

调试的方法多种多样,不同的调试方法适合不同的场景和人群。

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

  • 如果你在本地(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"] 值有变化时,也将其打印出来

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

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():  
    ...  

效果如下

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

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():  
    ...  

效果如下

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

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()  

运行一下,观察一下效果。

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

如果你要自定义格式输出的有很多个类型,那么 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) 的详细使用手册,是不是觉得还不错?

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

**-----**------**-----**---**** End **-----**--------**-----**-****

往期精彩文章推荐:

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

欢迎各位大佬点击链接加入群聊【helloworld开发者社区】:https://jq.qq.com/?_wv=1027&k=mBlk6nzX进群交流IT技术热点。

本文转自 https://mp.weixin.qq.com/s/TviIKtGgZuKhTLCkV50Bcw,如有侵权,请联系删除。

收藏
评论区

相关推荐

别再问我Python如何打包成exe了!
大家好,我是小五🐶 《老板又出难题,气得我写了个自动化软件》(https://mp.weixin.qq.com/s?__bizMzU5Nzg5ODQ3NQ&mid2247504512&idx1&sn9757d27acbb6c1b570fd4a2d376cd6e3&scene21wechat_redirect) 上次这篇文章中,评论区有好
几个常用js库,别再重复造轮子了
年底了,总结下今年用到的一些有意思的《js轮子》(只是大概列出些比较有意思的库,每个标题都是超链接,可点击自行查阅) 希望能对您有用! 如有意思的 轮子 可以在评论列出一起讨论下 color(https://links.jianshu.com/go?tohttps%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fco
Python 字典 使用技巧
1.遍历字典的3种方式Python3中:pythond {'x': 1, 'y': 2, 'z': 3}1.遍历keys:pythonfor k in d: print(k) print(dkey)或者pythonfor k in d.keys(): print(k) print(dkey)2.遍历val
别再问我Python打包成exe了!(终极版)
大家好,我是小五🐶上次这篇文章中,评论区有好几条留言都是关心如何将python脚本打包成10多M的?那今天小五就给大家全面总结一下:Python如何打包成exe,以及如何打得足够小。标准打包目前比较常见的打包exe方法都是通过Pyinstaller来实现的,本文也将使用这种常规方法。如果对这块已经很熟悉的小伙伴,可以直接下滑到本文下半部分。 为什么要打包?众
尤大都说了,别用parcel了。但我还是整出了一款Vue3项目搭建工具parcel-vue-app
先放图弱弱的说一句,我真的错了。不该用parcel前几天,尤大开直播说了造轮子的好处,主要还是锻炼自己。所以自己还是义无反顾地把这个项目搭建工具慢慢地造起来。这次升级这次版本是v1.0.5。我们来看下这次版本的预装依赖,我们的项目UI框架预先安装上了antdesignvue@2.1.4,默认是按需加载。为什么这次是引入它,而不是elementplus,主要是
https://cloud.tencent.com/developer/article/write/1830331
一、目标今天的目标是这个sign和appcode 二、步骤 Jadx没法上了app加了某梆的企业版,Jadx表示无能为力了。 FRIDADEXDumpDexDump出来,木有找到有效的信息。 Wallbreaker葫芦娃的Wallbreaker可以做些带壳分析,不过这个样本,用Frida的Spawn模式可以载入,Attach模式会失败。而直接用Objecti
ONNX 开始
环境 基础 bashconda create n onnx python3.8 yconda activate onnx ONNX https://github.com/onnx/onnxconda install c condaforge onnx ypython c "import onnx; print(onnx.version)"pyimport
一篇文章教会你使用Python中三种简单的函数
一、函数简介大家好,我是Go进阶者。所谓函数,就是指:把某些特定功能的代码组成为一个整体,这个整体就叫做函数。 二、函数定义和调用什么是函数的定义:相当于自己定义了一个能完成某些事件的功能;就好比自己打造了一个工具。定义函数格式:def test(): print('嘻嘻') print('这是我的第一个函数')什么是函数调用:如果仅仅是定义了
小白看过来,今天带你了解python2和python3的区别
看到这个题目大家可能猜到了我接下来要讲些什么,呵呵,对了,那就是列出这两个不同版本间的却别!搜索一下大家就会知道,python有两个主要的版本,python2 和 python3 ,但是python又不同于其他语言,向下兼容,python3是不向下兼容的,但是绝大多数组件和扩展都是基于python2的,下面就来总结一下python2和python3的区别。
618抢购抢不到?,会了python的这个骚操作,妈妈再也不担心我抢不过别人了!!!
618马上要到了,像淘宝,天猫,京东早就已经准备好了,每到618与双十一这种消费盛典,便会抢购的现象,很多人因为手速不够快,抢不到价格实惠的商品,在这小编给大家带来了一个自动抢购的示例代码,此代码是python通过selenium实现毫秒级的自动抢购。(该文章仅作学习selenium框架的学习示例)直接上源码:!/usr/bin/env python cod
NDK开发前奏 - 实现支付宝人脸识别功能
1. 基于 Android Studio 的 opencv 配置与使用先推荐一本书 《计算机视觉 算法与应用》,相信用过 OpenCV 的哥们都知道这是用来干啥的,这里我就不再啰嗦。只说一下他的应用领域:人机互动、物体识别、图像分割、人脸识别、动作识别、运动跟踪、机器人、运动分析、机器视觉、结构分析、汽车安全驾驶等等。这次我们主要用它来做人脸识别,注意人脸
求求你调试Python代码,不要再用Print了!
相信大部分人学习Python,肯定会用print()这个内置函数,来调试代码的。 那么在一个大型的项目中,如果你也是使用print来调试你的Python代码,你就会发现你的终端有多个输出。 那么你便不得不去分辨,每一行的输出是哪些代码的运行结果。 举个例子,运行下面这个程序。 num1  30 num2  40  print(num1
一篇文章带你了解Django ORM操作(进阶篇)
回顾上次咱们学习了一下Django ORM的基本查询操作。查询操作主要使用的是filter()方法。我们知道filter()查询出来的是值,如果想取第一个值需要再filter().first()才行。还知道了get()和filter().first()的区别等等。Django ORM的查询还有很多,继续来看叭!!!查询操作 对象.外键字段比如,我们拿到了一个
求你了,别再用 print 调试代码了
大家好,我是明哥。对于每个程序开发者来说,调试几乎是必备技能。代码写到一半卡住了,不知道这个函数执行完的返回结果是怎样的?调试一下看看代码运行到一半报错了,什么情况?怎么跟预期的不一样?调试一下看看调试的方法多种多样,不同的调试方法适合不同的场景和人群。 如果你是刚接触编程的小萌新,对很多工具的使用还不是很熟练,那么 print 和 log 大法好
爬虫进阶 - 前后端分离有什么了不起,过程超详细
这是一个详细的爬虫进阶教程,里面包含了很详细的思考和试错过程,如果你对学爬虫是认真的,建议认真看。 我们要抓取下面这个网站上的所有图书列表: https://www.epubit.com/books 1) 探索研究 创建一个新的python文件,写入如下代码:import requests url  'https://www.epubit.com/boo