写点什么

Python 调试神器之 PySnooper

用户头像
Jackpop
关注
发布于: 1 小时前

添加微信:code_7steps,备注“进群”,邀请你加入大牛云集的技术交流群!

前言

在程序开发过程中,代码的运行往往会和我们预期的结果有所差别。于是,我们需要清楚代码运行过程中到底发生了什么?代码哪些模块运行了,哪些模块没有运行?输出的局部变量是什么样的?很多时候,我们会想到选择使用成熟、常用的 IDE 使用断点和 watches 调试程序,或者更为基础的 print 函数、log 打印出局部变量来查看是否符合我们预期的执行效果。但是这些方法都有一个共同的弱点--效率低且繁琐,本文就介绍一个堪称神器的 Python 调试工具 PySnooper,能够大大减少调试过程中的工作量。

装饰器

装饰器(Decorators)是 Python 里一个很重要的概念,它能够使得 Python 代码更加简洁,用一句话概括:装饰器是修改其他函数功能的函数。PySnooper 的调用主要依靠装饰器的方式,所以,了解装饰器的基本概念和使用方法更有助于理解 PySnooper 的使用。在这里,我先简单介绍一下装饰器的使用,如果精力有限,了解装饰器的调用方式即可。


对于 Python,一切都是对象,一个函数可以作为一个对象在不同模块之间进行传递,举个例子,


def one(func):    print("now you are in function one.")    func()
def two(): print("now you are in function two")
one(two)
# 输出>>> now you are in function one.>>> now you are in function two.
复制代码


其实这就是装饰器的核心所在,它们封装一个函数,可以用这样或那样的方式来修改它。换一种方式表达上述调用,可以用**@+函数名**来装饰一个函数。


def one(func):    print("now you are in function one.")    def warp():        func()    return warp

@onedef two(): print("now you are in function two.")
two()
# 输出>>> now you are in function one.>>> now you are in function two.
复制代码


此外,在调用装饰器时还可以给函数传入参数:


def one(func):    print("now you are in function one.")    def warp(*args):        func(*args)    return warp

@onedef two(x, y): print("now you are in function two.") print("x value is %d, y value is %d" % (x, y))
two(5, 6)
# 输出>>> now you are in function one.>>> now you are in function two.>>> x value is 5, y value is 6
复制代码


另外,装饰器本身也可以接收参数,


def three(text):    def one(func):        print("now you are in function one.")        def warp(*args):            func(*args)        return warp    print("input params is {}".format(text))    return one

@three(text=5)def two(x, y): print("now you are in function two.") print("x value is %d, y value is %d" % (x, y))
two(5, 6)
# 输出>>> input params is 5>>> now you are in function one.>>> now you are in function two.>>> x value is 5, y value is 6
复制代码


上面讲述的就是 Python 装饰器的一些常用方法。

Pysnooper

调试程序对于大多数开发者来说是一项必不可少的工作,当我们想要知道代码是否按照预期的效果在执行时,我们会想到去输出一下局部变量与预期的进行比对。目前大多数采用的方法主要有以下几种:


  • Print 函数

  • Log 日志

  • IDE 调试器


但是这些方法有着无法忽视的弱点:


  • 繁琐

  • 过度依赖工具


"PySnooper is a poor man's debugger."


有了 PySnooper,上述问题都迎刃而解,因为 PySnooper 实在太简洁了,目前在 github 已经 10k+star。


前面花了一段篇幅讲解装饰器其实就是为了 PySnooper 做铺垫,PySnooper 的调用就是通过装饰器的方式进行使用,非常简洁。


PySnooper 的调用方式就是通过**@pysnooper.snoop**的方式进行使用,该装饰器可以传入一些参数来实现一些目的,具体如下:


参数描述 None 输出日志到控制台 filePath 输出到日志文件,例如'log/file.log'prefix 给调试的行加前缀,便于识别 watch 查看一些非局部变量表达式的值 watch_explode 展开值用以查看列表/字典的所有属性或项 depth 显示函数调用的函数的 snoop 行


安装


使用 pip 安装,


pip install pysnooper 
复制代码


或者使用 conda 安装,


conda install -c conda-forge pysnooper
复制代码


使用


先写一个简单的例子,


import numpy as npimport pysnooper

@pysnooper.snoop()def one(number): mat = [] while number: mat.append(np.random.normal(0, 1)) number -= 1 return mat

one(3)
# 输出
Starting var:.. number = 322:17:10.634566 call 6 def one(number):22:17:10.634566 line 7 mat = []New var:....... mat = []22:17:10.634566 line 8 while number:22:17:10.634566 line 9 mat.append(np.random.normal(0, 1))Modified var:.. mat = [-0.4142847169210746]22:17:10.634566 line 10 number -= 1Modified var:.. number = 222:17:10.634566 line 8 while number:22:17:10.634566 line 9 mat.append(np.random.normal(0, 1))Modified var:.. mat = [-0.4142847169210746, -0.479901983375219]22:17:10.634566 line 10 number -= 1Modified var:.. number = 122:17:10.634566 line 8 while number:22:17:10.634566 line 9 mat.append(np.random.normal(0, 1))Modified var:.. mat = [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]22:17:10.634566 line 10 number -= 1Modified var:.. number = 022:17:10.634566 line 8 while number:22:17:10.634566 line 11 return mat22:17:10.634566 return 11 return matReturn value:.. [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]
复制代码


这是最简单的使用方法,从上述输出结果可以看出,PySnooper 输出信息主要包括以下几个部分:


  • 局部变量值

  • 代码片段

  • 局部变量所在行号

  • 返回结果


也就是说,把我们日常 DEBUG 所需要的主要信息都输出出来了。


上述结果输出到控制台,还可以把日志输出到文件,


@pysnooper.snoop("debug.log")
复制代码



在函数调用过程中,PySnooper 还能够显示函数的层次关系,使得一目了然,


@pysnooper.snoop()def two(x, y):    z = x + y    return z

@pysnooper.snoop()def one(number): k = 0 while number: k = two(k, number) number -= 1 return number

one(3)
# 输出Starting var:.. number = 322:26:08.185204 call 12 def one(number):22:26:08.186202 line 13 k = 0New var:....... k = 022:26:08.186202 line 14 while number:22:26:08.186202 line 15 k = two(k, number) Starting var:.. x = 0 Starting var:.. y = 3 22:26:08.186202 call 6 def two(x, y): 22:26:08.186202 line 7 z = x + y New var:....... z = 3 22:26:08.186202 line 8 return z 22:26:08.186202 return 8 return z Return value:.. 3Modified var:.. k = 322:26:08.186202 line 16 number -= 1Modified var:.. number = 222:26:08.186202 line 14 while number:22:26:08.186202 line 15 k = two(k, number) Starting var:.. x = 3 Starting var:.. y = 2 22:26:08.186202 call 6 def two(x, y): 22:26:08.186202 line 7 z = x + y New var:....... z = 5 22:26:08.186202 line 8 return z 22:26:08.186202 return 8 return z Return value:.. 5Modified var:.. k = 522:26:08.186202 line 16 number -= 1Modified var:.. number = 122:26:08.186202 line 14 while number:22:26:08.186202 line 15 k = two(k, number) Starting var:.. x = 5 Starting var:.. y = 1 22:26:08.186202 call 6 def two(x, y): 22:26:08.186202 line 7 z = x + y New var:....... z = 6 22:26:08.186202 line 8 return z 22:26:08.186202 return 8 return z Return value:.. 6Modified var:.. k = 622:26:08.186202 line 16 number -= 1Modified var:.. number = 022:26:08.186202 line 14 while number:22:26:08.186202 line 17 return number22:26:08.186202 return 17 return numberReturn value:.. 0
复制代码


从上述输出结果可以看出,函数 one 与函数 two 的输出缩进层次一目了然。


除了缩进之外,PySnooper 还提供了参数 prefix 给 debug 信息添加前缀的方式便于识别,


@pysnooper.snoop(prefix="funcTwo ")def two(x, y):    z = x + y    return z

@pysnooper.snoop(prefix="funcOne ")def one(number): k = 0 while number: k = two(k, number) number -= 1 return number

one(3)
# 输出funcOne Starting var:.. number = 3funcOne 22:28:14.259212 call 12 def one(number):funcOne 22:28:14.260211 line 13 k = 0funcOne New var:....... k = 0funcOne 22:28:14.260211 line 14 while number:funcOne 22:28:14.260211 line 15 k = two(k, number)funcTwo Starting var:.. x = 0funcTwo Starting var:.. y = 3funcTwo 22:28:14.260211 call 6 def two(x, y):funcTwo 22:28:14.260211 line 7 z = x + yfuncTwo New var:....... z = 3funcTwo 22:28:14.260211 line 8 return zfuncTwo 22:28:14.260211 return 8 return zfuncTwo Return value:.. 3funcOne Modified var:.. k = 3funcOne 22:28:14.260211 line 16 number -= 1funcOne Modified var:.. number = 2funcOne 22:28:14.260211 line 14 while number:funcOne 22:28:14.260211 line 15 k = two(k, number)funcTwo Starting var:.. x = 3funcTwo Starting var:.. y = 2funcTwo 22:28:14.260211 call 6 def two(x, y):funcTwo 22:28:14.260211 line 7 z = x + yfuncTwo New var:....... z = 5funcTwo 22:28:14.260211 line 8 return zfuncTwo 22:28:14.260211 return 8 return zfuncTwo Return value:.. 5funcOne Modified var:.. k = 5funcOne 22:28:14.260211 line 16 number -= 1funcOne Modified var:.. number = 1funcOne 22:28:14.260211 line 14 while number:funcOne 22:28:14.260211 line 15 k = two(k, number)funcTwo Starting var:.. x = 5funcTwo Starting var:.. y = 1funcTwo 22:28:14.260211 call 6 def two(x, y):funcTwo 22:28:14.260211 line 7 z = x + yfuncTwo New var:....... z = 6funcTwo 22:28:14.260211 line 8 return zfuncTwo 22:28:14.260211 return 8 return zfuncTwo Return value:.. 6funcOne Modified var:.. k = 6funcOne 22:28:14.260211 line 16 number -= 1funcOne Modified var:.. number = 0funcOne 22:28:14.260211 line 14 while number:funcOne 22:28:14.260211 line 17 return numberfuncOne 22:28:14.260211 return 17 return numberfuncOne Return value:.. 0
复制代码


参数 watch 可以用于查看一些非局部变量,例如,


class Test():    t = 10

test = Test()

@pysnooper.snoop(watch=("test.t", "x"))
# 输出Starting var:.. number = 3Starting var:.. test.t = 10Starting var:.. x = 10
复制代码


参数 watch_explode 可以展开字典或者列表显示它的所有属性值,对比一下它和 watch 的区别,


#### watch_explode ####d = {    "one": 1,    "two": 1}

@pysnooper.snoop(watch_explode="d")
# 输出Starting var:.. number = 3Starting var:.. d = {'one': 1, 'two': 1}Starting var:.. d['one'] = 1Starting var:.. d['two'] = 1
#### watch ####d = { "one": 1, "two": 1}

@pysnooper.snoop(watch="d")
# 输出Starting var:.. d = {'one': 1, 'two': 1}
复制代码


可以看出 watch_explode 能够展开字典的属性值。


另外还有参数 depth 显示函数中调用函数的 snoop 行,默认值为 1,参数值需要大于或等于 1。

干货推荐

为了方便大家,我花费了半个月的时间把这几年来收集的各种技术干货整理到一起,其中内容包括但不限于 Python、机器学习、深度学习、计算机视觉、推荐系统、Linux、工程化、Java,内容多达 5T+,我把各个资源下载链接整理到一个文档内,目录如下:




所有干货送给大家,希望能够点赞支持一下!


https://pan.baidu.com/s/1eks7CUyjbWQ3A7O9cmYljA(提取码:0000)

用户头像

Jackpop

关注

还未添加个人签名 2020.09.16 加入

公众号:平凡而诗意,微信:code_7steps,全网粉丝超20万,技术进阶、优质资源、实用工具,欢迎关注!

评论

发布
暂无评论
Python调试神器之PySnooper