前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一种高效的调试方法

一种高效的调试方法

作者头像
赵云龙龙
发布2020-11-05 20:27:39
6010
发布2020-11-05 20:27:39
举报
文章被收录于专栏:python爱好部落

写代码的时候,很多精力都花在了调试上。 为了找出出错的罪魁祸首,一遍一遍的去debug。有的时候写代码的时间不多,但是要调通却花了很多时间。

1.1 传统调试模式 断点+单步调试 断点+单步调试估计是用的最多的了,对于较大型项目来说,其流程大致为:先在关键的代码位置加上print语句,通过分析print的值将范围缩小,这个过程可能需要重复多次,使用print的方法,一般可以将范围缩小到一个比较完整的功能模块中;然后在可能出现bug的模块中的关键部分打上断点,进入到断点后使用单步调试,查看各变量的值是否正确,最后根据错误的变量值定位到具体的代码行,最后进行修改 缺点 需要在代码中添加print语句,这就改变了原有的代码;

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

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

代码语言:javascript
复制
def one(func):
    print("now you are in function one.")
    func()

def two():
    print("now you are in function two")
one(two)

输出

代码语言:javascript
复制
now you are in function one.
now you are in function two.

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

代码语言:javascript
复制
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()

输出

代码语言:javascript
复制
now you are in function one.
now you are in function two.

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

代码语言:javascript
复制
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)

输出

代码语言:javascript
复制
now you are in function one.
now you are in function two.
x value is 5, y value is 6

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

代码语言:javascript
复制
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装饰器的一些常用方法。

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

Print函数 Log日志 IDE调试器 但是这些方法有着无法忽视的弱点:

繁琐 过度依赖工具

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

PySnooper使用起来十分简单,使用者可以在任何代码库中使用它,而无需进行任何设置。你只需添加装饰器,并为日志输出地址指定路径,方法是将其路径指定为第一个参数。

1.2 PySnooper调试模式 该工具使用采用装饰器的形式,将函数的运行过程以日志的形式打印到文件中,其记录了运行了哪些代码行,运行的时间及运行到当前代码时各变量的值。根据变量的变化就可以定位问题了。 优点: 无需为了查看变量的值,使用print打印变量的值,从而修改了原有的代码。 接口的运行过程以日志的形式保存,方便随时查看。 可以根据需要,设置函数调用的函数的层数,方便将注意力集中在需要重点关注的代码段。 多个函数的日志,可以设置日志前缀表示进行标识,方便查看时过滤。 二、PySnooper安装 pip install pysnooper 三、PySnooper简单使用 对需要调试的函数添加@pysnooper.snoop()装饰器

代码语言:javascript
复制
import pysnooper
@pysnooper.snoop()
def set_num2(a,b):
    if a == 1:
        c = a-b
    else:
        c = b/a
    return c

a = set_num2(1,2)
print(a)

执行结果

代码语言:javascript
复制
Source path:... E:/learn/lhytest/testpak/test1.py
Starting var:.. a = 1
Starting var:.. b = 2
15:02:13.679451 call        13 def set_num2(a,b):
15:02:13.679451 line        14     if a == 1:
15:02:13.679451 line        15         c = a-b
New var:....... c = -1
15:02:13.679451 line        18     return c
15:02:13.679451 return      18     return c
Return value:.. -1

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

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

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

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

代码语言:javascript
复制
@pysnooper.snoop("debug.log")

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

代码语言:javascript
复制
@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信息添加前缀的方式便于识别,

代码语言:javascript
复制
@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可以用于查看一些非局部变量,例如,

代码语言:javascript
复制
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的区别,

代码语言:javascript
复制
#### 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}

四、PySnooper参数说明 PySnooper以装饰器的形式使用,常用的五个参数为:output,overwrite, watch, depth, prefix。

代码语言:javascript
复制
def __init__(self, output=None, watch=(), watch_explode=(), depth=1,
                 prefix='', overwrite=False, thread_info=False, custom_repr=(),
                 max_variable_length=100):
    pass

参用参数 说明 output 该参数指定函数运行过程中产生的中间结果的保存位置,若该值为空,则将中间结果输出到控制台。 overwrite 与output配合使用,默认值=False,向记录文件中添加记录, 当设置为True时,将记录文件的历史记录删除,然后添加新的记录 watch 该参数是vector类型, 因为在默认情况下,装饰器只跟踪局部变量,要跟踪非局部变量,则可以通过该字段来指定。默认值为空vector。 depth 该参数表示需要追踪的函数调用的深度。在很多时候,我们在函数中会调用其他函数,通过该参数就可以指定跟踪调用函数的深度。默认值为1。 prefix 该参数用于指定该函数接口的中间结果前缀。当多个函数都使用的该装饰器后,会将这些函数调用的中间结果保存到一个文件中,此时就可以通过前缀过滤不同函数调用的中间结果。默认值为空字符串。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 python粉丝团 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档