今天写登录项目为了实现对账户vip等级的检测,将检测函数打包成可以传递vip等级的装饰器,回顾了一下装饰器知识,来记录一下个人的体悟.
看了几个大神的博客,对装饰器的解释如下:
假设我们要增强函数的功能,比如,在函数调用前后自动打印,但又不希望修改函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
我个人觉得说的太笼统,因为我下面的代码完成了上述的要求却不能称之为装饰器:
#传统意义的装饰器
def new(func):
def wrapper(*args, **kw):
print('############')
return func(*args, **kw)
return wrapper
#定义一个求和函数,再用@new对它进行装饰
@new
def add(a,b):
print(a+b)
pass
#功能与上面的一样,只是当要装饰其它函数时需要把函数作为参数传入,显得麻烦,所以我们需要传统装饰器
def myAdd(*args, **kw,func=add):
print('############')
return func(*args, **kw)
我查阅资料得知装饰器的原则组成如下:
< 函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器 >
这是装饰器的灵魂,我个人的理解为,装饰器必须满足他所规定的嵌套函数的结构,因为这样才能使用@的语法糖,而@语法糖才是装饰器的优点所在.
@语法糖到底是怎么实现的呢?
以上面代码为例来进行说明,给add函数加上@new装饰器其实等同于以下的代码
add = new(add)
所以我理解为:基于python万物皆对象的理论,将原本的add函数传入new(),其实相当于把add函数作为可调用的外部对象给了wrapper()函数,也可以理解为:在执行wrapper前,先给warpper传入了一个add方法.这点是装饰器的灵魂思想,因为上面我自己写的方法就无法做到每次自动传入我想要装饰的函数.这就是方法造成的差距,
函数对象有一个__name__属性,可以拿到函数的名字,我们打印装饰后的add.__name__得到的却是wrapper,所以说被装饰器修饰后的add其实是指向了wrapper函数,在一定程度上可以理解为:我们调用的其实是传递了原本add函数的wrapper方法.
理解了普通装饰器的规范和原理再来理解可以用装饰器传参的decorator的高阶函数就水到渠成了.结构如下:
def new(text):
def decorator(func):
def wrapper(*args, **kw):
print('####',text,'####')
return func(*args, **kw)
return wrapper
return decorator
使用方法如下:
@new('为所欲为')
def add(a,b):
print(a+b)
pass
#执行add(1,2)结果如下:
#### 为所欲为 ####
3
同理,用@new(c)装饰add等同于:
add=new(c)(add)
到这里可能会产生疑问:我们要实现用装饰器传递参数就必须用到三层函数的结构吗?套在最外的一层将所有需要的参数一次性传递进去不可以吗?
答案是肯定的不行的,这和一开始我自己写的myAdd方法比不上装饰器的道理一样,因为一次传几个参数,其中有的参数我们只想设置一次,但是因为有的参数必须每次执行时都得设置,导致前者也必须得每次设置.而装饰器每层函数传一个参数的结构使得它可以完成某些参数只设置一次的需求.
有一天,我的一个朋友对我说:装饰器只能在被修饰的函数前添加操作.
我想应该也有些初学者和我朋友有着同样错误的想法.
我朋友的理由是被装饰的方法是最后return才被调用的.乍一听好像有点道理,其实这是没理解装饰器的原理,只会生搬硬套造成的.
前面我们知道了装饰器可以说是构造了一个新的warpper方法,只是把add函数放了进去而已,至于说放哪里,这完全是你说了算,没必要死板的用return来调用,代码如下:
def new(func):
def wrapper(*args, **kw):
print('--------------------------')
func(*args, **kw)
print('************************')
pass
return wrapper
@new
def add(a,b):
print(a+b)
pass
add(2,3)
#执行结果如下
--------------------------
5
************************