Python调试神器之PySnooper

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 前面一篇文章介绍了一下Python调试工具包pdb,虽然能够避免print、log的繁琐和解脱对IDE的依赖,但是这依然不够让人眼前一亮甚至惊叹,本文介绍一款最近非常火热的Python调试神器,同时详细阐述一下Python装饰器的使用方法,另外文末附有福利。

前言

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

装饰器

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

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

1def one(func):
 2    print("now you are in function one.")
 3    func()
 4
 5
 6def two():
 7    print("now you are in function two")
 8
 9
10one(two)
11
12# 输出
13>>> now you are in function one.
14>>> now you are in function two

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

1defone(func):
2    print("now you are in function one.")
3    defwarp():
4        func()
5    return warp
6
7
8@one
9deftwo():
10    print("now you are in function two.")
11
12
13two()
14
15# 输出
16>>> now you are in function one.
17>>> now you are in function two.

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

1def one(func):
 2    print("now you are in function one.")
 3    def warp(*args):
 4        func(*args)
 5    return warp
 6
 7
 8@one
 9def two(x, y):
10    print("now you are in function two.")
11    print("x value is %d, y value is %d" % (x, y))
12
13
14two(5, 6)
15
16# 输出
17>>> now you are in function one.
18>>> now you are in function two.
19>>> x value is 5, y value is 6

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

1def three(text):
 2    def one(func):
 3        print("now you are in function one.")
 4        def warp(*args):
 5            func(*args)
 6        return warp
 7    print("input params is {}".format(text))
 8    return one
 9
10
11@three(text=5)
12def two(x, y):
13    print("now you are in function two.")
14    print("x value is %d, y value is %d" % (x, y))
15
16
17two(5, 6)
18
19# 输出
20>>> input params is 5
21>>> now you are in function one.
22>>> now you are in function two.
23>>> 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

使用

先写一个简单的例子,

1import numpy as np
 2import pysnooper
 3
 4
 5@pysnooper.snoop()
 6def one(number):
 7    mat = []
 8    while number:
 9        mat.append(np.random.normal(0, 1))
10        number -= 1
11    return mat
12
13
14one(3)
15
16# 输出
17
18Starting var:.. number = 3
1922:17:10.634566 call         6 def one(number):
2022:17:10.634566 line         7     mat = []
21New var:....... mat = []
2222:17:10.634566 line         8     while number:
2322:17:10.634566 line         9         mat.append(np.random.normal(0, 1))
24Modified var:.. mat = [-0.4142847169210746]
2522:17:10.634566 line        10         number -= 1
26Modified var:.. number = 2
2722:17:10.634566 line         8     while number:
2822:17:10.634566 line         9         mat.append(np.random.normal(0, 1))
29Modified var:.. mat = [-0.4142847169210746, -0.479901983375219]
3022:17:10.634566 line        10         number -= 1
31Modified var:.. number = 1
3222:17:10.634566 line         8     while number:
3322:17:10.634566 line         9         mat.append(np.random.normal(0, 1))
34Modified var:.. mat = [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]
3522:17:10.634566 line        10         number -= 1
36Modified var:.. number = 0
3722:17:10.634566 line         8     while number:
3822:17:10.634566 line        11     return mat
3922:17:10.634566 return      11     return mat
40Return value:.. [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]

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

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

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

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

1@pysnooper.snoop("debug.log")


94.jpg

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

1@pysnooper.snoop()
 2def two(x, y):
 3    z = x + y
 4    return z
 5
 6
 7@pysnooper.snoop()
 8def one(number):
 9    k = 0
10    while number:
11        k = two(k, number)
12        number -= 1
13    return number
14
15
16one(3)
17
18# 输出
19Starting var:.. number = 3
2022:26:08.185204 call        12 def one(number):
2122:26:08.186202 line        13     k = 0
22New var:....... k = 0
2322:26:08.186202 line        14     while number:
2422:26:08.186202 line        15         k = two(k, number)
25    Starting var:.. x = 0
26    Starting var:.. y = 3
27    22:26:08.186202 call         6 def two(x, y):
28    22:26:08.186202 line         7     z = x + y
29    New var:....... z = 3
30    22:26:08.186202 line         8     return z
31    22:26:08.186202 return       8     return z
32    Return value:.. 3
33Modified var:.. k = 3
3422:26:08.186202 line        16         number -= 1
35Modified var:.. number = 2
3622:26:08.186202 line        14     while number:
3722:26:08.186202 line        15         k = two(k, number)
38    Starting var:.. x = 3
39    Starting var:.. y = 2
40    22:26:08.186202 call         6 def two(x, y):
41    22:26:08.186202 line         7     z = x + y
42    New var:....... z = 5
43    22:26:08.186202 line         8     return z
44    22:26:08.186202 return       8     return z
45    Return value:.. 5
46Modified var:.. k = 5
4722:26:08.186202 line        16         number -= 1
48Modified var:.. number = 1
4922:26:08.186202 line        14     while number:
5022:26:08.186202 line        15         k = two(k, number)
51    Starting var:.. x = 5
52    Starting var:.. y = 1
53    22:26:08.186202 call         6 def two(x, y):
54    22:26:08.186202 line         7     z = x + y
55    New var:....... z = 6
56    22:26:08.186202 line         8     return z
57    22:26:08.186202 return       8     return z
58    Return value:.. 6
59Modified var:.. k = 6
6022:26:08.186202 line        16         number -= 1
61Modified var:.. number = 0
6222:26:08.186202 line        14     while number:
6322:26:08.186202 line        17     return number
6422:26:08.186202 return      17     return number
65Return value:.. 0

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

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

1@pysnooper.snoop(prefix="funcTwo ")
 2def two(x, y):
 3    z = x + y
 4    return z
 5
 6
 7@pysnooper.snoop(prefix="funcOne ")
 8def one(number):
 9    k = 0
10    while number:
11        k = two(k, number)
12        number -= 1
13    return number
14
15
16one(3)
17
18# 输出
19funcOne Starting var:.. number = 3
20funcOne 22:28:14.259212 call        12 def one(number):
21funcOne 22:28:14.260211 line        13     k = 0
22funcOne New var:....... k = 0
23funcOne 22:28:14.260211 line        14     while number:
24funcOne 22:28:14.260211 line        15         k = two(k, number)
25funcTwo     Starting var:.. x = 0
26funcTwo     Starting var:.. y = 3
27funcTwo     22:28:14.260211 call         6 def two(x, y):
28funcTwo     22:28:14.260211 line         7     z = x + y
29funcTwo     New var:....... z = 3
30funcTwo     22:28:14.260211 line         8     return z
31funcTwo     22:28:14.260211 return       8     return z
32funcTwo     Return value:.. 3
33funcOne Modified var:.. k = 3
34funcOne 22:28:14.260211 line        16         number -= 1
35funcOne Modified var:.. number = 2
36funcOne 22:28:14.260211 line        14     while number:
37funcOne 22:28:14.260211 line        15         k = two(k, number)
38funcTwo     Starting var:.. x = 3
39funcTwo     Starting var:.. y = 2
40funcTwo     22:28:14.260211 call         6 def two(x, y):
41funcTwo     22:28:14.260211 line         7     z = x + y
42funcTwo     New var:....... z = 5
43funcTwo     22:28:14.260211 line         8     return z
44funcTwo     22:28:14.260211 return       8     return z
45funcTwo     Return value:.. 5
46funcOne Modified var:.. k = 5
47funcOne 22:28:14.260211 line        16         number -= 1
48funcOne Modified var:.. number = 1
49funcOne 22:28:14.260211 line        14     while number:
50funcOne 22:28:14.260211 line        15         k = two(k, number)
51funcTwo     Starting var:.. x = 5
52funcTwo     Starting var:.. y = 1
53funcTwo     22:28:14.260211 call         6 def two(x, y):
54funcTwo     22:28:14.260211 line         7     z = x + y
55funcTwo     New var:....... z = 6
56funcTwo     22:28:14.260211 line         8     return z
57funcTwo     22:28:14.260211 return       8     return z
58funcTwo     Return value:.. 6
59funcOne Modified var:.. k = 6
60funcOne 22:28:14.260211 line        16         number -= 1
61funcOne Modified var:.. number = 0
62funcOne 22:28:14.260211 line        14     while number:
63funcOne 22:28:14.260211 line        17     return number
64funcOne 22:28:14.260211 return      17     return number
65funcOne Return value:.. 0

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

1class Test():
 2    t = 10
 3
 4
 5test = Test()
 6
 7
 8@pysnooper.snoop(watch=("test.t", "x"))
 9
10# 输出
11Starting var:.. number = 3
12Starting var:.. test.t = 10
13Starting var:.. x = 10

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

1#### watch_explode ####
 2d = {
 3    "one": 1,
 4    "two": 1
 5}
 6
 7
 8@pysnooper.snoop(watch_explode="d")
 9
10# 输出
11Starting var:.. number = 3
12Starting var:.. d = {'one': 1, 'two': 1}
13Starting var:.. d['one'] = 1
14Starting var:.. d['two'] = 1
15
16#### watch ####
17d = {
18    "one": 1,
19    "two": 1
20}
21
22
23@pysnooper.snoop(watch="d")
24
25# 输出
26Starting var:.. d = {'one': 1, 'two': 1}

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

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
安全 程序员 Python
Python中的异常处理与错误调试
【4月更文挑战第8天】本文探讨Python中的异常处理和错误调试,将其比喻为驾驶过程中的意外情况。异常是程序执行时的非正常事件,如文件缺失或网络故障,而错误是代码本身的逻辑或语法问题。Python通过try-except语句处理异常,确保程序在遇到问题时不会立即崩溃。错误调试则需定位问题根源,利用pdb等工具逐行检查代码。这两个技能对保持代码稳定性和可靠性至关重要,能增强程序应对意外的能力并帮助修复潜在问题。
40 1
|
6月前
|
开发框架 人工智能 Rust
Python 潮流周刊#12:Python 中如何调试死锁问题?
Python 潮流周刊#12:Python 中如何调试死锁问题?
49 0
|
2月前
|
监控 Python Windows
python知识点100篇系列-pysnooper用于调试
PySnooper是一个便捷的Python调试工具,用于监控代码执行过程及局部变量的变化,替代繁琐的打印语句。作为GitHub上的热门开源项目,它通过装饰器自动记录代码执行细节。安装简便,支持多种平台,可通过pip安装。使用时,只需在目标函数上添加装饰器即可实时查看变量变化或将其记录至日志文件。此外,还支持使用with块对特定代码段进行调试。更多详细信息可参阅其官方使用文档。
python知识点100篇系列-pysnooper用于调试
|
3月前
|
JavaScript 前端开发 ice
简单实用,Python代码调试利器~
简单实用,Python代码调试利器~
45 4
简单实用,Python代码调试利器~
使用icecream优雅调试Python代码
在大型项目中,使用print()调试代码可能导致终端输出过多,难以分辨输出结果与代码的对应关系。为了更清晰地调试,可以采用Icecream库。通过使用Icecream,可以更有效地进行Python代码调试,同时保持代码的整洁性。
|
3月前
|
程序员 开发工具 Python
[oeasy]python0030_动态控制断点_breakpoints_debug_调试
[oeasy]python0030_动态控制断点_breakpoints_debug_调试
35 2
|
3月前
|
搜索推荐 JavaScript 前端开发
简单实用,Python代码调试利器/java代码的设计和解读
尽管有许多高级调试工具,但在多数情况下,`print()`仍是便捷之选。`icecream`库则将`print()`调试法发挥到极致,简化变量检查与信息输出,提升调试效率。无论是基本变量还是复杂数据结构,`icecream`都能轻松应对,并支持自定义输出格式,让你的调试工作更高效。下面,让我们一起探索`icecream`的更多实用功能吧!
20 0
|
3月前
|
搜索推荐 JavaScript 前端开发
简单实用,Python代码调试利器
简单实用,Python代码调试利器
|
3月前
|
监控 Java Serverless
Serverless 应用的监控与调试问题之PyFlink对于Python UDF的性能如何提升
Serverless 应用的监控与调试问题之PyFlink对于Python UDF的性能如何提升
|
4月前
|
Shell 程序员 开发工具
[oeasy]python0026_调试程序_pdb3_帮助_help_求助_文档
调试程序_debug_next_下一步_list_pdb3 🥋
47 1