装饰器是python实现AOP的一种方式,是程序开发过程中经常用到的一个功能。符合“开放封闭”原则,也就是说,已经实现的功能代码不允许修改,但可以被扩展。
首先,需要理解的是,python中所有的定义(函数、属性、类等等)都是引用,比如如下:
def test(num): print(num) test(100) print(test) print(id(test))
运行结果如下:

分析一下,其实在定义test方法时,就是在内存开辟了一块新地址存放函数体prnt(num),而函数名test只是指向了这个内存地址。所以当python解释器执行到test(100),会找到这个函数体,将参数带进去执行。python解释器执行到print(test)的时候,也会发现test就是这个内存地址的引用,而内存地址里面就是一个函数体,所以就想这个函数体打印出来。而id(test)就是讲test引用的内存地址打印出来。

理解了上面以后,这里在引入一个概念——“闭包”
闭包就是在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,那么这个函数以及用到的外部函数的变量就称为闭包,可以看一下如下的代码,在函数test内部有定义了一个函数test2,而test2用到了test的num,这就是一个“闭包”。
def test(num): print(num)
#闭包 def test2(): print(num + 1) return test2 res = test(100) #100 res() #101
运行结果:

好了,接下来有一个需求,目前有fi、f2、f3三个函数,在调用这三个函数之前进行权限验证,但是又要做到“开放封闭”的原则,既不能动f1、f2、f3原来的代码,这时候就要用到闭包:
def fun(f): def innerFun(): print("=====权限验证相关=====") f() return innerFun def f1(): print("=====f1=====") def f2(): print("=====f2=====") def f3(): print("=====f3=====") # 调用f1 res = fun(f1) res() # 调用f2 res = fun(f2) res() # 调用f3 res = fun(f3) res()
运行结果:

由此可见,使用闭包,可以实现权限验证。但是这样却存在一个问题,本来调用f1只需要f1(),而现在调用f1则需要先调用fun并且将f1传入,再调用其返回。所有调用f1的地方都需要更改。这时候呢用装饰器来重新实现这个需求;
def fun(f): print("=====正在进行装饰=====") def innerFun(): print("=====权限验证相关=====") f() return innerFun #python解释器运行到@符号开头的这一行,就已经开始装饰了,而不是调用的时候才开始,装饰后的结果就是f1指向了fun内部的innerFun @fun def f1(): print("=====f1=====") #这时候已经装饰过了 f1()
运行结果:

简单分析一下原理:
代码定义了两个函数,f1指向的函数块内存地址是4136587,fun指向的函数体内部又有一个函数innerFun,并且接受一个形参f。

当python解释器执行到@fun的时候,开始装饰。既相当于调用了fun(f1),这时候f1将自己的医用传给了fun函数的形参f既f指向了id为4136587的函数体,而f1重新指向了fun内部的innerFun的地址。

此时调用f1(),相当于是执行fun内部的innerFun函数,先进行权限相关验证,在执行f(),而f指向的是id为4136587的函数体的内存地址,既print("f1")。
这里需要注意的一点是,python解释器运行到@fun的时候就已经开始进行装饰了,而只有在调用f1()的时候才会进行验证(既运行innerFun里面的代码),从上面的结果可以看到,使用装饰器无需求该原来的代码,只需要在定义f1函数的地方加上一个“语法糖”,既@fun
现在我们来看看,当有多个装饰器的时候,Python解释器的执行顺序又是怎样的呢
def fun1(f): print("=====正在进行装饰fun1=====") def innerFun(): print("=====权限验证相关fun1=====") return("==fun1=="+f()+"==fun1==") return innerFun def fun2(f): print("=====正在进行装饰fun2=====") def innerFun(): print("=====权限验证相关fun2=====") return("==fun2=="+f()+"==fun2==") return innerFun #python解释器装饰的顺序是先装饰fun2,在装饰fun1,在装饰fun2的时候,f1就指向了fun2内部的innerFun函数,而装饰fun1的时候,f1指向了这个f1 @fun1 @fun2 def f1(): return("===f1===") #在调用f1的时候,由于后装饰fun1,所以此时f1指向的实际是fun1内部的innerFun函数,所以先执行fun1内部的innerFun函数 #当执行到fun1内部的innerFun函数中 return("==fun1=="+f()+"==fun1==") 时,在调用这个f()的时候又去了fun2内部的innerFun函数 #所以最终打印出来的结果是"==fun1====fun2=====f1=====fun2====fun1==" print(f1())
运行结果:

根据之前的分析,也就很好理解为什么先装饰fun2,后装饰fun1,而先验证fun1后验证fun2了。
对于这种无参的函数,使用上面的装饰器没什么问题,但是如果是有参数的函数呢?根据分析,当装饰之后,f1指向了fun内部的innerFun,但是innerFun并不需要参数。而且需要装饰的函数可能会有很多,有些是需要参数的,有些是不需要参数的,而且参数的个数也不尽相同,这里就要用到不定长参数了。
def fun(f): print("=====正在进行装饰fun=====") #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典 def innerFun(*args,**kwargs): print("=====权限验证相关fun=====") f(*args,**kwargs) return innerFun @fun def f1(): print("===f1===") @fun def f2(num): print(num) @fun def f3(parm1,parm2): print(parm1+parm2) f1() f2(3) f3("hello","77")
运行结果:

现在不管函数是有参数的还是无参数的,都已经支持了,那么如果是带有返回值的函数,要怎么进行装饰呢?如果按照之前的方法,代码如下
def fun(f): print("=====正在进行装饰fun=====") #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典 def innerFun(*args,**kwargs): print("=====权限验证相关fun=====") f(*args,**kwargs) return innerFun @fun def f2(info): print(info) return(info) res = f2("hello world") print("=====return value=====:%s",res)
此时,实际需要的是调用f2.并将info返回给res并打印,但是实际运行结果如下:

由此可见,实际打印出来的info是hello world 而res接收到的info却是None,为什么会这样呢?根据之前的分析,在python解释器执行到 @fun 的时候就已经开始进行装饰,装饰完以后f2不再指向原来的函数体,而是指向了fun内部的函数体,即innerFun,而innerFun并没有返回值,所以最终res接收到的就是None,那么该怎么解决这个问题呢?其实只要在innerFun内部将调用f的地方返回就可以了,代码如下:
def fun(f): print("=====正在进行装饰fun=====") #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典 def innerFun(*args,**kwargs): print("=====权限验证相关fun=====") return f(*args,**kwargs) return innerFun @fun def f1(): print("===f1===") @fun def f2(info): print(info) return(info) res1 = f1() res2 = f2("hello world") print("return value : %s,%s"%(res1,res2))
运行结果:

这样来看的话,现在已经是一个通用的装饰器了,不管函数是否有参数,也不管函数是否有返回值,都可以进行装饰。接下来再说一种情况,在某些应用场景下,需要在装饰器本身需要参数,然后根据参数进行不同的操作。此时就需要在装饰器外面再封装一层,用来接收参数,代码如下:
def fun_arg(arg = "hello python"): def fun(f): print("=====正在进行装饰fun=====") #不定长参数,*args接收的是一个元组,**kwargs接收的是一个字典 def innerFun(*args,**kwargs): print("=====权限验证相关fun=====") print(arg) return f(*args,**kwargs) return innerFun return fun #执行步骤:首先调用fun_arg("Python")函数,最后将fun的引用返回回来,此时@fun_arg("Python")就相当于@fun,然后@fun对函数进行装饰 @fun_arg("Python") def f1(): print("===f1===") @fun_arg() def f2(info): print(info) return(info) res1 = f1() res2 = f2("hello world") print("return value : %s,%s"%(res1,res2))
运行结果如下:

其实当python解释器执行到@fun_arg("Python")的时候就直接去调用了@fun_arg("Python"),而fun_arg函数最后将fun函数的引用返回回来了,这时候@fun_arg("Python")执行完以后就相当于@fun,也就是之前的装饰器,@fun再对函数进行装饰。
至此,装饰器基本用法已经全部演示完了。
浙公网安备 33010602011771号