欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python之装饰器

程序员文章站 2022-07-05 11:39:26
(希望在看此篇随笔前能了解"闭包"这个概念,因为"装饰器"就是基于"闭包"实现的) 现有以下场景: 公司里,测试团队反馈你们开发写的app用起来反应太慢了,老板给你下了个命令,需要你检查一下所有函数的执行效率,看看是否因为某些函数导致的"反应慢" 急老板之所急,你很快响应了老板的命令. 分解一下,什 ......

(希望在看此篇随笔前能了解"闭包"这个概念,因为"装饰器"就是基于"闭包"实现的)

现有以下场景:

公司里,测试团队反馈你们开发写的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()    # 运行一次就可以打印它的效率了

结果:python之装饰器,成功了!......但是......

此时内心毫无波动,因为你知道,这只是对一个函数修改就这么麻烦,项目里那么多函数呢...

"一个个去改?不可能的,我这辈子都不可能一个个去改的!"就是你此时的内心独白.

你想到了"捷径":可以把添加的新代码单独拿出来,包装成另外一个函数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...

>>>如有幸被转载,请标注出处,也算是对得起我熬夜拼出来的这些字吧.谢过<<<

>>>如有幸被转载,请标注出处,也算是对得起我熬夜拼出来的这些字吧.谢过<<<

>>>如有幸被转载,请标注出处,也算是对得起我熬夜拼出来的这些字吧.谢过<<<