本文共 6909 字,大约阅读时间需要 23 分钟。
装饰器
什么是装饰器?它有什么作用?我们先不去纠结这些,先从一个需求来入手。
需求:
在一个公司里面,有好几个部门A、B、C、D,B、C、D三个部门都会使用到A部门编写的一系列函数,但是他们并不参与函数内部的修改与编写,只能调用这些函数,获得函数返回的结果或者使用函数实现的功能。那么问题来了,老板发话说,不能让什么人都可以随便调用我们的函数,比如新人未转正的。那就需要让每个人在调用函数之前,进行身份验证,如果权限不足的人员是无法调用这些接口的,这样就避免了没有经验的人员胡乱调用接口,使、接口出现不可预知的错误,影响公司运营。
那么该怎么实施呢?老板找了几个员工,让他们每个人都提供一套解决方案。下面我们就来看看这几位员工提供了什么方法: 员工1: 员工1让跑到各个部门分别告诉每个员工,让他们没有转正的员工不能调用这些接口,只能有经验的员工才能调用。 员工2: 员工2是没有采取员工1的方法,而是准备在函数内部动脑筋:def f1(): #验证1 print('f1')def f2(): #验证1 print('f2')def f3(): #验证1 print('f3')
员工3:
员工3是这样做的:def checkLogin(): #验证1def f1(): checkLogin() print('f1')def f2(): checkLogin() print('f2')def f3(): checkLogin() print('f3')
现在我们应该能看的出来哪个员工的方法有优势了吧。员工1用口头的话去限制别人,那肯定是不可行的,员工2虽然加了验证,但是万一接口数量巨多,那无疑又增加了很多代码。而员工3在员工2的基础上单独定义了一个验证函数,然后在每一个函数内部调用这个验证函数,这个方法已经实现的很好了!但是!还是不行,因为你要深入到每一个函数的内部去调用这个验证函数,无形中修改了我们既有的函数,这不符合开放封闭原则。什么是开放封闭原则呢?通俗点说就是:不能修改原有功能的代码,但是可以拓展!因此员工3的方案也没能得到老板的认可!
那么到底要怎么样在不改变原有功能代码的情况下,去拓展功能呢?接下来我们就引入装饰器,先来看看一下代码:def w1(func): def inner(): #验证代码 func() return inner@w1def f1(): print('f1')@w1def f2(): print('f2')@w1def f3(): print('f3')
那这段代码是什么意思呢?函数w1看得懂吧,这是闭包!不懂的可以去看看我的另一篇介绍闭包的博客:
以上代码执行顺序: @w1 def f4(): 先执行w1函数,在执行f4函数. 先看看装饰器的效果,我们不需要深入函数内部修改代码,也不需要使用大量重复代码,只是定义了一个闭包函数,然后在每一个要调用函数的顶部加上@闭包函数,就可以实现功能,而且还满足开放封闭原则。 那么什么是装饰器呢?我们先来分析一下装饰器的原理:装饰器的原理
红框其实就是装饰器的原理.
我们将test1给set_func函数传递过去. 然后调用ret(). 执行逻辑: 1.执行set_func() , 将test1传递给func参数. 2.返回内部函数call_func , 给ret 3.调用ret() , 相当于调用了 call_func 4.执行权限验证 5.调用func() , 相当于调用了test1 但是我们这里调用的是ret(),难道要把每一个调用test1的地方都改成ret吗?那不就又变得很麻烦了吗?接下来看下面的程序:ret可以改名为test1. 然后调用test1即可.
调用test1(),其实就是调用了 call_func. 执行权限验证之后,再执行原来的test1. 注意当将test1作为参数传递过去的时候, 这个参数就指向了原来的test1. 所以后来给test1覆盖变为 set_func的返回值是完全没有问题的.为什么要给ret 改为test1?
这样做的话, 原来函数调用都不用修改,但是执行的过程其实已经变了, 该加的验证也加 了.
这就是装饰器. 不修改原来代码,但修改逻辑. 下面来小结一下: 上面一个程序中,程序从上到下执行,首先定义了一个函数set_func,然后里面嵌套了一个函数call_func,而函数set_func的参数被内嵌函数call_func使用,而且函数set_func返回内嵌函数的引用,很明显这是一个闭包函数。 接着下面,定义了一个test1函数,代表了我们已经实现好的而且不能更改内部功能的函数。再向下,调用set_func函数而且把函数的引用test1当做参数传递进去,即此时func=test1,然后set_func函数返回call_func的引用,那么此时,test1=call_func,但是之前当做参数传入的test1保持不变,它还是指向test1函数。接下来执行test1(),因为此时 test1=call_func,那其实就是调用函数call_func并执行,装饰器开始对test1进行装饰,首先打印出:—这是权限验证1—-和—这是权限验证2—,接下来就是调用并执行fun函数,在此之前func=test1,那么在此处执行func(),就是在执行test1(),于是去调用test1函数,打印出—–test1—-。这就实现了先执行权限验证程序,只有验证程序通过了,再去调用接口函数,这就是装饰器的原理。装饰器的理解
装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限验证等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以腠理出大量与函数本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
讲完了原理与概念,我们就回到正规的装饰器写法上来,刚才只是通过例子来介绍装饰器的原理,那么真正的装饰器程序格式应该如下:def w1(fun): print('...装饰器开始装饰...') def inner(): print('...验证权限...') fun() return inner@w1def test(): print('test')test()
执行结果如下:
...装饰器开始装饰......验证权限...test
其实当python解释器读取到@w1时,就开始对test函数开始修饰了,此时test已经不是指向原来的test()了,而是指向闭包函数外函数返回的内函数的引用,指向test函数的test已经被被传入到闭包函数中了,此时fun=test,即变量fun保存的是test函数的引用。
对有参数的函数进行装饰
以上讲解的都是对无参数的函数的装饰,那么装饰器对有参数的函数是怎么装饰的呢?请看下面的代码段:
def set_func(func): def call_func(num): #这里要使用形参接收实参 print('这是权限验证代码') func(num) #这里要使用形参接收实参 return call_func@set_funcdef test1(num): print("这是功能函数————%s" % num)test1(100)
逻辑分析:
程序执行到@set_func 的时候,开始对test1进行装饰,即此时test1=set_func(test1),而set_func的返回值为call_func,即此时test1=call_func,而func指向原始功能函数test1,执行test1(100)等同于执行call_func(100),然后执行装饰代码对原始功能函数test1进行装饰,然后执行func(100),等同于执行原始功能函数test1(100),则开始执行功能函数,此时参数num也传进去了。对不定长参数的函数进行装饰
def w_add(func): print("开始装饰") def inner(*args, **kwargs): print('add inner called') func(*args, **kwargs) return inner@w_adddef add(a, b,**kwargs): print('%d + %d = %d' % (a, b, a + b)) print(kwargs)@w_adddef add2(a, b, c,**kwargs): print('%d + %d + %d = %d' % (a, b, c, a + b + c)) print(kwargs)add(2, 4,m=100)add2(2, 4, 6,m=200)
对带返回值的函数进行装饰
def set_func(func): def call_func(): print('set_func inner called start') str = func() print('set_func inner called end') return str return call_func@set_funcdef test(): print('this is test fun') return 'hello world'ret = test()print('ret value is %s' % ret)
输出结果为:
set_func inner called startthis is test funset_func inner called endret value is hello world
两个装饰器装饰一个函数
def set_func(fun1): print('----a----') def call_func1(): print('----1----') return '' + fun() + '' return call_func1def set_func(fun2): print('----b----') def call_func2(): print('----2----') return '' + fun() + '' return call_func2@set_func1@set_func2def test(): print('----c----') print('----3----') return 'life is short,you need python'ret = test()print(ret)
输出结果为:
----b--------a--------1--------2--------c--------3----hello python decorator
程序分析:
程序从上到下执行,执行到def的时候,都是在定义一个函数,并不去执行。当执行到@set_func1的时候,准备进行装饰,于是向下寻找一个函数,但是发现下面并不是一个函数,而是另外一个装饰器,于是set_func1暂停装饰,先让set_func2进行装饰,那么此时新test=set_func2(原test),即原函数test的引用被当做参数传入进set_func2中,使得 新test=call_func2,而call_func2中的fun就指向了原test函数。在这里不要晕,弄清楚哪个是原test函数,哪个是新test函数。此时就会打印出b,set_func2装饰完成后返回到@set_func1在去执行@set_func2的装饰,那么此时就会将 新test(即新test=call_func2)当做参数传入到set_func1中,而此时 新新test=set_func1(新test),那么也就有 新新test=call_func1,而call_func1中的fun=新test。 那么当程序执行到ret=test()时,此时的test=新新test,指向set_func1中的call_func1,则会打印出1,然后执行:return '' + fun() + ''
那么根据以上分析可以知道,set_func1中的fun函数执行新test函数(set_func2中的call_func2函数),于是去调用call_func2,然后打印出2,然后执行:
return '' + fun() + ''
又以为set_func1中的fun指向原test函数,然后就去调用原test函数,打印出c和3,接着return ‘life is short,you need python’字符串,返回到call_func2中,组合成
'' + 'life is short,you need python' + ''
接着继续向上返回,返回到set_func1中,组合成:
'' + '' + 'life is short,you need python' + '' + ''
因此最后打印出:
hello python decorator
所以最后的结果为:
----b--------a--------1--------2--------c--------3----hello python decorator
附上一张流程图:
带参数的装饰器
def func_args(pre='xiaoqiang'): def w_test_log(func): def inner(): print('...记录日志...visitor is %s' % pre) func() return inner return w_test_log# 带有参数的装饰器能够起到在运行时,有不同的功能# 先执行func_args('wangcai'),返回w_test_log函数的引用# @w_test_log# 使用@w_test_log对test_log进行装饰@func_args('wangcai')def test_log(): print('this is test log')test_log()
输出结果为:
...记录日志...visitor is wangcaithis is test log
通用装饰器
其实,不管是装饰带参数的函数还是不带参数的函数,我们都可以使用一个通用的装饰器来装饰他们,如下:
def set_func(func): def call_func(*args, **kwargs): ret=func(*args, **kwargs) return ret return call_func
即参数全部使用*args和**kwargs来传递,可以传递参数也可以不传递,都不会报错。
类装饰器
class Test(object): def __init__(self, func): print('test init') print('func name is %s ' % func.__name__) self.__func = func def __call__(self, *args, **kwargs): print('装饰器中的功能') self.__func()@Testdef test(): print('this is test func')test()
输出结果:
test initfunc name is test 装饰器中的功能this is test func
程序分析:
当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。转载地址:https://blog.csdn.net/weixin_40612082/article/details/80569784 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!