写代码的时候,很多精力都花在了调试上。 为了找出出错的罪魁祸首,一遍一遍的去debug。有的时候写代码的时间不多,但是要调通却花了很多时间。
1.1 传统调试模式 断点+单步调试 断点+单步调试估计是用的最多的了,对于较大型项目来说,其流程大致为:先在关键的代码位置加上print语句,通过分析print的值将范围缩小,这个过程可能需要重复多次,使用print的方法,一般可以将范围缩小到一个比较完整的功能模块中;然后在可能出现bug的模块中的关键部分打上断点,进入到断点后使用单步调试,查看各变量的值是否正确,最后根据错误的变量值定位到具体的代码行,最后进行修改 缺点 需要在代码中添加print语句,这就改变了原有的代码;
装饰器 装饰器(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装饰器的一些常用方法。
调试程序对于大多数开发者来说是一项必不可少的工作,当我们想要知道代码是否按照预期的效果在执行时,我们会想到去输出一下局部变量与预期的进行比对。目前大多数采用的方法主要有以下几种:
Print函数 Log日志 IDE调试器 但是这些方法有着无法忽视的弱点:
繁琐 过度依赖工具
在断点调试和单步调试过程中,需要保持持续的专注,一旦跳过了关键点就要从头开始。
PySnooper使用起来十分简单,使用者可以在任何代码库中使用它,而无需进行任何设置。你只需添加装饰器,并为日志输出地址指定路径,方法是将其路径指定为第一个参数。
1.2 PySnooper调试模式 该工具使用采用装饰器的形式,将函数的运行过程以日志的形式打印到文件中,其记录了运行了哪些代码行,运行的时间及运行到当前代码时各变量的值。根据变量的变化就可以定位问题了。 优点: 无需为了查看变量的值,使用print打印变量的值,从而修改了原有的代码。 接口的运行过程以日志的形式保存,方便随时查看。 可以根据需要,设置函数调用的函数的层数,方便将注意力集中在需要重点关注的代码段。 多个函数的日志,可以设置日志前缀表示进行标识,方便查看时过滤。 二、PySnooper安装 pip install pysnooper 三、PySnooper简单使用 对需要调试的函数添加@pysnooper.snoop()装饰器
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)
执行结果
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所需要的主要信息都输出出来了。
上述结果输出到控制台,还可以把日志输出到文件,
@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 = 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}
四、PySnooper参数说明 PySnooper以装饰器的形式使用,常用的五个参数为:output,overwrite, watch, depth, prefix。
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 该参数用于指定该函数接口的中间结果前缀。当多个函数都使用的该装饰器后,会将这些函数调用的中间结果保存到一个文件中,此时就可以通过前缀过滤不同函数调用的中间结果。默认值为空字符串。