此为个人学习笔记,内容仅供参考,转载请注明作者,如有错误,欢迎指正。)
记得以前学到这里,看了很多关于python装饰器的文章,对于装饰器还是一知半解,后来在一些实例中, 通过最朴实的方法总算是摸清楚了什么是装饰器?以下是个人对装饰器的笔记。
在学习装饰器之前,我们必须搞清楚一点,装饰器究竟是什么? 用文字说明, 装饰器实际上就是为了给某个函数,或者称某个程序添加一个功能。并且考虑 到该函数或程序已经被引用,所以我们不能对函数或程序本身进行修改,所以 有了装饰器,可以想象成,我们人拿上斧头,拥有了砍树的能力,这就是装饰器的作用。
装饰器需要满足以下两点:
ps:此处如果不理解,我们还用人砍树的例子。比如说小明他是个人,你要让他砍树,他怎么砍, 必须要让他拥有砍树能力,那么问题来了,怎么让他拥有这个能力呢?好吧可以给他注射药剂, 让他变强,但是这就不满足上述第一点了,被改造后的小明我们就不认识他了,不知道怎么让他去砍树, 所以就必须有前提条件1,不得修改源代码。 第二条,不改变调用方法。原本把我想让小明去砍树就对 小明下命令,“嘿小明去砍个树”,但是小明不会啊,怎么办呢,让一个会的人带着小明去,这个过程就成了什么呢? 你让某个人带着小明去,而不是你让小明去,这就出现了调用方法的改变。
定义一个测试的函数,返回输入的值
def test1(a):
return a
上述定义的函数,它的作用就是返回输入值,那么接下来要考虑给它加一个计算它自身运行时间的功能。最简单的思路是,记录开始时间->运行函数->记录结束时间, 用结束时间减去开始时间就是我们想要的结果了,如下:
import time
start_time = time.time() # 开始时间
test1(250) # 要测试的函数
stop_time = time.time() # 结束时间
delta_time = stop_time - start_time # 时间间隔,即运行时间
好了,简单地实现了。根据需求应该把这个过程封装在函数内,调用一次就返回test1的运行时间。塞到函数里面大概是这样:
def test2()
start_time = time.time() # 开始时间
test1(250) # 要测试的函数
stop_time = time.time() # 结束时间
delta_time = stop_time - start_time # 时间间隔,即运行时间
print('{:f}'.format(delta_time)) # 格式化打印
运行一下
>>> test2(250)
'0.000001'
此处可以看到,将上述过程封装到 test2 之后,通过调用 test2 就能得到 test1 函数的运行时间, 但是不难发现有两个点:
(1)改变了调用方法,根据装饰器的描述,应该直接调用test1就得到运行时间
(2)此处只研究了test1(250)也就是说此时test1只有一个固定参数,这不能保证是否会因为参数的不同运行时间不同。
针对这两个点进一步改进
首先来解决一下关于调用的时候不再是固定参数,如下:
def test2(func):
def test3(x):
start_time = time.time() # 开始时间
func(x) # 要测试的函数
stop_time = time.time() # 结束时间
delta_time = stop_time - start_time # 时间间隔,即运行时间
return '{:f}'.format(delta_time) # f返回格式化时间
return test3
def test1(a):
return a
运行一下
>>> test2(test3)(250)
'0.000002'
现在有三个函数test1, test2, test3。整个流程还是得梳理清楚,是为了计算test1的运行时间, 那么还是"记录开始时间->运行函数->记录结束时间"这一个流程。
在上面这段代码中,首先把 test1 的函数地址当成参数传入到了 test2 , 此时 test2(func) 相当于 test2(test1),在 test2 中,再定义一个test3。把上述求运行时间的流程放进去,在test3中可以看到一个func(x), func是我们传入的地址也就是test1,要计算test1的运行时间,那肯定是需要调用的, func() 就是 func 的调用,func(x) 相当于 test1(x)。
再来看 test2 函数的返回值,是test3的地址,一方面不会因为调用而报错,只是拿了 test3 函数的地址; 一方面是为了取得 test3 以保证后面的展开。现在目的就很明确了。调用test2, 传入test1的地址,又因为是返回了test3的地址,实际上经过一顿操作,test2(test1) 就像是 test3 的地址, test2(test1)() 就是在实例化 test3 了, 即 test3(), 显而易见,其中 x 即最初传入的 250 。现在可以这样来获得各种参数中test1的运行时间。
刚刚说到两个点,已经解决了一个,再去解决调用方法。在test1函数加一个@test2,这是一个语法糖, 效果如下:
def test2(func):
def test3(x):
start_time = time.time() # 开始时间
func(x) # 要测试的函数
stop_time = time.time() # 结束时间
delta_time = stop_time - start_time # 时间间隔,即运行时间
return '{:f}'.format(delta_time) # f返回格式化时间
return test3
@test2
def test1(a):
return a
运行一下
>>> test1(250)
'0.000001'
加入语法糖之后直接调用test1就得出了想要的结果。那么这个语法糖的作用就显而易见了。 就是替代test2(test1)的。
就是说test2(test1)(250) 直接被语法糖省略成了test1(250),这二者是相同的效果,但是满足了不改变调用方法,而且我们自始至终都没有修改过test1里面的源代码。
这就是装饰器。(2018.11.16)