当前位置: 移动技术网 > IT编程>脚本编程>Python > python之装饰器

python之装饰器

2018年04月21日  | 移动技术网IT编程  | 我要评论

电车之狼小游戏,于清斌,电子商务师培训

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

现有以下场景:

公司里,测试团队反馈你们开发写的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...

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

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

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网