Python调试神器之PySnooper

前言

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

如果有想要学习Python或者正在学习Python中的小伙伴,需要学习资料的话,可以到我的微信公众号:Python学习知识圈,后台回复:“01”,即可拿Python学习资料

装饰器

装饰器(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
​
​
@one
def 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
​
​
@one
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)
​
# 输出
>>> 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 np
import 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 = 3
22: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 -= 1
Modified var:.. number = 2
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, -0.479901983375219]
22:17:10.634566 line        10         number -= 1
Modified var:.. number = 1
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, -0.479901983375219, 1.0491540468063252]
22:17:10.634566 line        10         number -= 1
Modified var:.. number = 0
22:17:10.634566 line         8     while number:
22:17:10.634566 line        11     return mat
22:17:10.634566 return      11     return mat
Return value:.. [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]

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

  • 局部变量值
  • 代码片段
  • 局部变量所在行号
  • 返回结果

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

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

@pysnooper.snoop("debug.log")

image
image.gif

​​

在函数调用过程中,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 = 3
22:26:08.185204 call        12 def one(number):
22:26:08.186202 line        13     k = 0
New var:....... k = 0
22: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:.. 3
Modified var:.. k = 3
22:26:08.186202 line        16         number -= 1
Modified var:.. number = 2
22: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:.. 5
Modified var:.. k = 5
22:26:08.186202 line        16         number -= 1
Modified var:.. number = 1
22: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:.. 6
Modified var:.. k = 6
22:26:08.186202 line        16         number -= 1
Modified var:.. number = 0
22:26:08.186202 line        14     while number:
22:26:08.186202 line        17     return number
22:26:08.186202 return      17     return number
Return 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 = 3
funcOne 22:28:14.259212 call        12 def one(number):
funcOne 22:28:14.260211 line        13     k = 0
funcOne New var:....... k = 0
funcOne 22:28:14.260211 line        14     while number:
funcOne 22:28:14.260211 line        15         k = two(k, number)
funcTwo     Starting var:.. x = 0
funcTwo     Starting var:.. y = 3
funcTwo     22:28:14.260211 call         6 def two(x, y):
funcTwo     22:28:14.260211 line         7     z = x + y
funcTwo     New var:....... z = 3
funcTwo     22:28:14.260211 line         8     return z
funcTwo     22:28:14.260211 return       8     return z
funcTwo     Return value:.. 3
funcOne Modified var:.. k = 3
funcOne 22:28:14.260211 line        16         number -= 1
funcOne Modified var:.. number = 2
funcOne 22:28:14.260211 line        14     while number:
funcOne 22:28:14.260211 line        15         k = two(k, number)
funcTwo     Starting var:.. x = 3
funcTwo     Starting var:.. y = 2
funcTwo     22:28:14.260211 call         6 def two(x, y):
funcTwo     22:28:14.260211 line         7     z = x + y
funcTwo     New var:....... z = 5
funcTwo     22:28:14.260211 line         8     return z
funcTwo     22:28:14.260211 return       8     return z
funcTwo     Return value:.. 5
funcOne Modified var:.. k = 5
funcOne 22:28:14.260211 line        16         number -= 1
funcOne Modified var:.. number = 1
funcOne 22:28:14.260211 line        14     while number:
funcOne 22:28:14.260211 line        15         k = two(k, number)
funcTwo     Starting var:.. x = 5
funcTwo     Starting var:.. y = 1
funcTwo     22:28:14.260211 call         6 def two(x, y):
funcTwo     22:28:14.260211 line         7     z = x + y
funcTwo     New var:....... z = 6
funcTwo     22:28:14.260211 line         8     return z
funcTwo     22:28:14.260211 return       8     return z
funcTwo     Return value:.. 6
funcOne Modified var:.. k = 6
funcOne 22:28:14.260211 line        16         number -= 1
funcOne Modified var:.. number = 0
funcOne 22:28:14.260211 line        14     while number:
funcOne 22:28:14.260211 line        17     return number
funcOne 22:28:14.260211 return      17     return number
funcOne Return value:.. 0

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

class Test():
    t = 10
​
​
test = Test()
​
​
@pysnooper.snoop(watch=("test.t", "x"))
​
# 输出
Starting var:.. number = 3
Starting var:.. test.t = 10
Starting var:.. x = 10

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

#### watch_explode ####
d = {
    "one": 1,
    "two": 1
}
​
​
@pysnooper.snoop(watch_explode="d")
​
# 输出
Starting var:.. number = 3
Starting var:.. d = {'one': 1, 'two': 1}
Starting var:.. d['one'] = 1
Starting 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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容