强烈推荐一个Python debug神器

  • Post author:
  • Post category:python


导语:最近在

github

上冒出了一个

python



debug

神器

PySnooper

,号称在

debug

时可以消灭

print

。那么该工具有哪些优点呢,如何使用该工具呢。本文就介绍该工具的优缺点和使用方式。

前言

使用

python

开发过程中,总是避免不了

debug

。传统的

debug

过程大致分为两种:

断点+单步调试。

断点+单步调试估计是用的最多的了,对于较大型项目来说,其流程大致为:先在关键的代码位置加上

print

语句,通过分析

print

的值将范围缩小,这个过程可能需要重复多次,使用

print

的方法,一般可以将范围缩小到一个比较完整的功能模块中;然后在可能出现bug的模块中的关键部分打上断点,进入到断点后使用单步调试,查看各变量的值是否正确,最后根据错误的变量值定位到具体的代码行,最后进行修改。


pdb

调试。


pdb



python

自带的一个包,为

python

程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。

pdb

的调试流程和1)基本差不多,其具体的使用方法大家可以网上搜一下。

传统的

debug

的方法的缺点包括:

a)需要在代码中添加

print

语句,这就改变了原有的代码;

b)在断点调试和单步调试过程中,需要保持持续的专注,一旦跳过了关键点就要从头开始。

最近在

github

上冒出了一个

debug

工具,可以解决传统

debug

过程中的缺点。下面一块来看看这个工具的使用和神奇之处。

1. PySnooper是什么

790f4e870b37981cc2131f898f47e4c6.jpeg

该工具使用采用装饰器的形式,将函数的运行过程以日志的形式打印到文件中,其记录了运行了哪些代码行,运行的时间及运行到当前代码时各变量的值。根据变量的变化就可以定位问题了。亲自试用该工具后,其优点可总结为以下几点:

1、无需为了查看变量的值,使用

print

打印变量的值,从而修改了原有的代码。

2、接口的运行过程以日志的形式保存,方便随时查看。

3、可以根据需要,设置函数调用的函数的层数,方便将注意力集中在需要重点关注的代码段。

4、多个函数的日志,可以设置日志前缀表示进行标识,方便查看时过滤。

该工具有这么多优点,那么如何使用呢,下面结合

demo

来介绍该工具的使用。

2. 使用方式介绍

1. 工具安装

pip install pysnooper

2. 官方demo介绍

官方demo代码:

import pysnooper
@pysnooper.snoop()
def number_to_bits(number):
    if number:
        bits = []
        while number:
            number, remainder = divmod(number, 2)
            bits.insert(0, remainder)
        return bits
    else:
        return [0]
number_to_bits(6)

控制台输出:

e51dfc2cbeaef5ae5168ebdef5b46461.jpeg

控制台的输出如上图,从图中可以看到,从进入到函数开始,会记录每一行代码的执行及记录新增局部变量或已有局部变量的变化,直到函数结束。以装饰器的形式使用该工具后,会将函数运行的中间结果打印出来,这样方便后续的

bug

定位和分析。

3. 参数介绍

以装饰器的形式使用该工具,其包含了四个参数,参数包括

output

,

variables

,

depth

,

prefix

,如下图。

40633cf1fe5955a51ac59580833af0c5.jpeg

1、

output

参数。该参数指定函数运行过程中产生的中间结果的保存位置,若该值为空,则将中间结果输出到控制台。

2、

variables

参数。该参数是

vector

类型, 因为在默认情况下,装饰器只跟踪局部变量,要跟踪非局部变量,则可以通过该字段来指定。默认值为空

vector

3、

depth

参数。该参数表示需要追踪的函数调用的深度。在很多时候,我们在函数中会调用其他函数,通过该参数就可以指定跟踪调用函数的深度。默认值为1。

4、

prefix

参数。该参数用于指定该函数接口的中间结果前缀。当多个函数都使用的该装饰器后,会将这些函数调用的中间结果保存到一个文件中,此时就可以通过前缀过滤不同函数调用的中间结果。默认值为空字符串。

3. 工具应用

要使用该工具只需要理解该装饰器(

snoop

)的参数的含义,下面结合几个

demo

介绍参数的使用及对结果的影响。

1. output 参数使用

若使用默认参数,则将中间结果输出到控制台,若填写该参数,则将中间结果写入到该参数指定的目录下,如运行以下代码,其中间结果会保存在装饰器

snoop

中设置日志保存的路径中,注意这里不会自动创建目录,所以需要事先创建目录,如测试代码中填写路径后需要创建

log

目录。

测试代码:

import pysnooper

def add(num1, num2):
    return num1 + num2

@pysnooper.snoop("./log/debug.log", prefix="--*--")
def multiplication(num1, num2):
    sum_value = 0
    for i in range(0, num1):
        sum_value = add(sum_value, num2)
    return sum_value

value = multiplication(3, 4)

运行该代码后,在

./log/debug.log

的内容如下:

5da8aeee44284da5a8ea0483f216ea27.jpeg

从运行代码的中间结果中可以看出,文件中记录了各行代码的执行过程及局部变量的变化。在

debug

时,通过分析该文件,就可以跟踪每一步的执行过程及局部变量的变化,这样就能快速的定位问题所在;由于运行的中间结果保存在文件中,方便随时分析其运行的中间结果,也便于共享。

2. variables参数使用

在默认参数的情况下,使用该工具只能查看局变量的变化过程,当需要查看局部变量以外变量时,则可以通过

variables

参数进行设置,比如下方代码,在

Foo

类型,需要查看类实例的变量

self.num1

,

self.num2

,

self.sum_value

,则可以看将该变量设置当参数传入

snoop

的装饰器中。

测试代码:

import pysnooper

class Foo(object):
    def __init__(self):
        self.num1 = 0
        self.num2 = 0
        self.sum_value = 0

    def add(self, num1, num2):
        return num1 + num2
    @pysnooper.snoop(output="./log/debug.log", variables=("self.num1", "self.num2", "self.sum_value"))
    def multiplication(self, num1, num2):
        self.num1 = num1
        self.num2 = num2
        sum_value = 0
        for i in range(0, num1):
            sum_value = self.add(sum_value, num2)
        self.sum_value = sum_value
        return sum_value

foo = Foo()
foo.multiplication(3, 4)

为了体现该参数的作用,这里分别使用默认参数和上述参数(代码中设置的参数)运行代码,得到的结果如下:

0cd2582ba99199622f2e0e9a47bb93ca.jpeg

使用默认参数的结果

3a00fba6eb3e8edc915746ba724954c1.jpeg

使用代码中参数的结果

从两个中间结果中可以看出,若变量不是局部变量,哪怕在函数中使用了该变量,如果不显示设置打印该变量的中间结果,则不会将该变量的中间结果打印到文件中。

3. depth参数使用

该参数用来指定记录函数调用层数的结果,默认值为1,若要查看多层函数调用的中间结果,则可将该参数设置为>=2。

测试代码:

import pysnooper

def add(num1, num2):
    return num1 + num2

@pysnooper.snoop("./log/debug.log", depth=2)
def multiplication(num1, num2):
    sum_value = 0
    for i in range(0, num1):
        sum_value = add(sum_value, num2)
    return sum_value

value = multiplication(3, 4)

为了对比,将

depth

的值分别设置为1和2,其结果如下:

f79a6f378256f13fcc4664f628199ffc.jpeg


depth=1

的结果

12ccade0883cf998ff956e1a5dfee0b1.jpeg


depth=2

的结果

从上述结果中可以看出,若要查看更深层次函数调用的情况,则可以通过设置

depth

值进行查看。这样方便用户有选择性的查看函数的调用情况。

4.

prefix

参数使用

该参数主要用于设置中间结果的前缀,这样就可以区分不同的函数调用的中间结果,默认参数为””。

测试代码:

import pysnooper

def add(num1, num2):
    return num1 + num2

@pysnooper.snoop("./log/debug.log", prefix="--*--")
def multiplication(num1, num2):
    sum_value = 0
    for i in range(0, num1):
        sum_value = add(sum_value, num2)
    return sum_value

value = multiplication(3, 4)

运行代码后的中间结果如下:

5e92aa19f9f8dc1e5d4e161ac06dfd26.jpeg

从结果中可以看到,中间结果的每一行都包含了

prefix

设置的前缀,这样便于区分不同的函数调用的中间结果。

上述的介绍为了将注意力集中到具体的参数,采取设置单一参数的形式进行介绍(

output

+其他单个参数)。在实际使用时,可以同时设置多个参数。使用

PySnooper

工具来记录函数运行的中间结果,比起传统的使用断点+单步调试,

pdb

等调试方法,

PySnooper

工具有着巨大的优势。

4. 该工具的不足之处

虽然使用

debug

在使用

PySnooper

很方便,但还是存在一些问题(以4月26号拉取代码为依据),比如:

1、无法很好的支持递归调用。

2、调用每个函数的中间结果只能保存在一个文件中,如果需要区分不同文件的结果,需要使用

prefix

来进行前缀标识。

3、对于跨文件函数调用,不支持记录调用函数所在的文件名。

当然

PySnooper

是最近在

github

上火起来的项目,还不够完善是正常的,相信这些不足之处后续也会得到完善,期待一个更好的

PySnooper


NO.


1

往期推荐

Historical articles


【原创】用Matplotlib绘制的图表,真的是太惊艳了!!


嫌Python代码慢?这个模块让Python提效100倍!!


【原创】用Python来绘制甘特图并制作可视化大屏,太方便了!!


超实用,精心整理了34个Python+Excel+Word+PDF自动化库!

分享、收藏、点赞、在看安排一下?

3804abcb11d74917560ec13dcd33181e.gif

ea9483a6d52b20d8eb5b4eaeef672795.gif

0b4717b3d0e78e09f28c4295174dcacc.gif

df7eff041fff001bb878f99e3d550a60.gif