(希望在看此篇随笔前能了解"闭包"这个概念,因为"装饰器"就是基于"闭包"实现的)
现有以下场景:
公司里,测试团队反馈你们开发写的app用起来反应太慢了,老板给你下了个命令,需要你检查一下所有函数的执行效率,看看是否因为某些函数导致的"反应慢"
急老板之所急,你很快响应了老板的命令.
分解一下,什么是某函数的执行效率呢?拿程序员的入门函数举例:
1 def func(): 2 print("Hello World!") 3 print("这是我的第一个python函数!")
在这个函数在被调用前,来个计时开始,在函数执行完之后来个计时结束,前后时间差就是这个函数的执行时间,也就是所谓的执行效率.
所以,这是要给这个func()添加一个新功能,叫"输出执行效率".
怎么做呢?
要添加功能,那么当然是改编它了,然后就有了下面的改编方式:
1 import time 2 def func(): 3 starttime = time.time() 4 print("Hello World!") # 原函数语句 5 print("这是我的第一个python函数!") # 原函数语句 6 time.sleep(1) # 因为这个入门函数执行速度太快,起始时间差太短,所以手动给它加上1秒睡眠时间以方便显示 7 endtime = time.time() 8 print("func()函数的执行效率为%s秒" % (endtime - starttime)) 9 10 func() # 运行一次就可以打印它的效率了
结果:,成功了!......但是......
此时内心毫无波动,因为你知道,这只是对一个函数修改就这么麻烦,项目里那么多函数呢...
"一个个去改?不可能的,我这辈子都不可能一个个去改的!"就是你此时的内心独白.
你想到了"捷径":可以把添加的新代码单独拿出来,包装成另外一个函数timer(),它接收其他函数名为参数:
1 import time 2 def timer(f): 3 starttime = time.time() 4 f() 5 endtime = time.time() 6 print("%s函数的执行效率为%s秒" % (f.__name__, (endtime - starttime))) 7 8 timer(func) # 测试func()的执行效率
# timer(func1) # 测试func1()的执行效率
现在快被自己聪明哭了,分分钟替领导排忧解难,势必会得到领导的夸赞...
但是...瞬间一个冷颤: 我要知道func()函数的执行效率,就需要改变它原来的执行方式func()为timer(func)了,这样来说,要在项目运行中知道某个函数的执行效率,我也要这样修改它的执行方式了啊,这得改多少地方???万万不可...
怎么办?!?!?!改,就得改所有代码,那会累死.不改,那老板会把我neng死...怎么办?!?!?!
苦思冥想中,突然想到,如果我重新命名一下我的timer(),让它伪装成func(),这样一来,在func()原先被调用的地方,现在还是func()被调用啊!
# 以下代码为了区分变量和函数,使用不同颜色标记出长得一样的两个量
1 new_func = func # 将函数名func赋值给变量"new_func",那么new_func现在就是个函数了 2 func = timer #将函数名timer复制给变量"func". '''
注意此处的func变量与上面的函数名func除了长得一样外没有任何关系,但是必须长得一样,因为这是个"伪装" 回过头来看,因为python程序是由上至下按行执行的,所以后面的项目代码在调用func()的时候,实质上就是调用了func,也就是timer()
而timer()在调用的时候需要传参,把谁传进去呢?当然是把需要执行的func传进去,此时func已经赋值给了new_func,所以应该写成timer(new_func)
最后就是func(new_func)也就是timer(func),相当于使用timer()把func()的效率测试了一遍
'''
那么问题来了,原项目在调用func()时不会给它传参,只是单纯地调用它而已,而且还加了两句话...怎么办???
继续修改,在timer()函数里内嵌一个函数,外层函数的返回值设为内层函数的函数名:
1 import time 2 def timer(f): 3 def inner(): 4 starttime = time.time() 5 f() 6 endtime = time.time() 7 print("%s函数的执行效率为%s秒" % (f.__name__, (endtime - starttime))) 8 return inner
# 接下来,把timer(func)赋值给func,再执行func().原理同上,一定要捋清楚谁是谁!
func = timer(func) # func = inner
func() # 执行func()也就等同于执行inner(),即完成了"测试func()的执行效率",同时也没有改变原项目对func()的调用
# (也就是看起来一样,实际不一样,这是个真正的"伪装"),而且只加了一句话func = timer(func)做了一个关系转换.
这样就完成了一个"最简单版"的装饰器(姑且称之为timer v1.0).到此,已完成所需的功能.
但是中的但是,python语法三大特点: 简洁,优美,清晰.它觉得你加的这句func = timer(func)还是不够简洁优美清晰,怎么办???
其他编程语言都提供了一个东西,叫"语法糖"(语法糖指那些实质上没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法.语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读).那么python也有语法糖,在装饰器这里就>>>用"@"符号,接装饰器的函数名<<<置于需要被装饰的函数前面,如:
1 def timer(): 2 ... # 为节省空间,此处就不详述timer()的构造 3 @timer # 意思就是func = timer(func) 4 def func(): 5 print("Hello World!") 6 print("这是我的第一个python函数!")
至此,完成了装饰器v1.0的升级(此时应该叫timer v2.0了).
(也许有疑问,如果项目中有1000个函数,那还是需要加1000个@timer啊?....对!但是,需求不应该是现在才提出来,而是在项目代码构建之初就有,所以在项目代码编写的时候,顺手一个@timer就完成了啰啰嗦嗦的好几句话,是不是方便很多了!)
装饰器一般用来干嘛: 给函数增加"打印日志","测试效率","登录认证"等等额外功能的同时,还可以不改变原函数的调用方式
还没做完.
反观以上的func()函数,是没有参数的.如果现在需要测试一个带参数的函数呢?比如:
def sum(x, y): return x+y
sum(111, 222)
直接@timer()就不行了.怎么办?!?!?!
继续改!!!
上面sum(111, 222)把111和222传给了timer v2.0里的inner(),所以inner()必须能接收111和222.怎么写?
可以写成inner(a, b)
但是,如果是下面这个函数呢?
def multiple(x, y, z): return x * y * z
又或者是个参数为字典/列表/元祖的函数呢?怎么办???
那么python又机智地为你预想到了,给你提供了这么一个"动态参数"的方式来接收任意函数传过来的参数:
...
inner(*args, **kwargs)
...
对应的,inner()里需要执行的函数f()需要接收这些参数,所以也需要变成:
... f(*args, **kwargs)
...
所以,timer()改写成:
def timer(f): def inner(*args, **kwargs): starttime = time.time() f(*args, **kwargs) # 借助inner将参数传给f endtime = time.time() print("%s函数的执行效率为%s秒" % (f.__name__, (endtime - starttime))) return inner
此时是(timer v3.0)了
问题又双叒叕来了,如果要装饰一个带返回值的函数呢? 怎么办? 怎? 么? 办?
好办!再inner里把f(*args, **kwargs)执行结果赋值给一个变量ret,最后inner返回ret是不是就ok了:
def inner(*args, **kwargs): starttime = time.time() ret = f(*args, **kwargs) # 借助inner将参数传给f,结果赋值给ret endtime = time.time() print("%s函数的执行效率为%s秒" % (f.__name__, (endtime - starttime))) return ret # 返回ret的值,即函数的返回值 return inner
至此,大功告成!终极版timer装饰器诞生!!!
timer v4.0(版本都超过了python3.6了!)
另: 多个装饰器可以装饰同一个函数,在函数前一行一个装饰器即可,执行时也是按python代码的执行顺序执行装饰器
后续:
装饰器这块,理解起来很绕很绕,一定要多多多多捋.最好的理解方法: 写一步就在那步后面手动贴上执行结果,一步步捋!
>>>装饰器的终极版模板:
def deco(f): def inner(*args, **kwargs): '''执行被装饰函数 之前的操作''' ret = f(*args, **kwargs) '''执行被装饰函数 之后的操作''' return ret return inner
End<<<
(为了交作业也是拼了...可以安心睡觉了...)
To Be Continued...
>>>如有幸被转载,请标注出处,也算是对得起我熬夜拼出来的这些字吧.谢过<<<
>>>如有幸被转载,请标注出处,也算是对得起我熬夜拼出来的这些字吧.谢过<<<
>>>如有幸被转载,请标注出处,也算是对得起我熬夜拼出来的这些字吧.谢过<<<