一文理解python装饰器
一、python中,装饰器是什么(decorator)
装饰器是python中的一个很重要的概念,它的作用,顾名思义是装饰一个东西,既然是装饰,也暗示着,不会去改变被装饰的东西。
学过初中数学的都知道函数。比如一个简单的函数 y=Ax+b ,也可以写成 y= f(x)=Ax+b,装饰器其实就和数学中的函数一样,传入一个 变量x, 通过f(x)这个函数,把结果变成y, 并且x本身是不变的。而我们使用装饰器,也是为了在不改变原函数的内部结构的前提下,对原函数进行功能的扩展和修改(这个同样是为了符合开放封闭准则)。
总结:装饰器的基本过程是python函数接受一个函数作为参数 并返回一个函数
二、装饰器可以实现关键因素
上面总结的:装饰器的基本过程是python函数接受一个函数作为参数 并返回一个函数,对这句话,我们推敲一下关键点。
关键点一:python的参数是不是可以是函数,并且返回值也是一个函数,为什么可以这样
关键点二:是通过什么方式去扩展这个函数的功能
对于关键点一:我们常说,python中一切皆对象,函数也是对象,所以,函数也可以作为另一个函数的参数被传入 ,同理也可以返回一个函数。这个更详细一点的可以搜索 python一切皆对象的原因,这个不多阐述。
对于关键点二:实现这个扩展的功能是依靠一个叫 闭包的方式实现的,这个可以自行学一下,也可以参考下面程序先这样理解,闭包是一个函数A里嵌套了一个函数B,函数A最终返回函数B。这样的话,当我们调用函数A(),实际就是返回了一个函数B,如下
def A(fun):
def B():
fun()
return B
result=A(fun)# 这个值返回的是函数 B
resutlt() # 这个就等价于B()
上面的 B函数内部是可以调用传入的参数 fun。 原本在函数A调用过后(执行result=A(fun)这个一步后 ),因为函数A里面参数 fun是个局部变量,会被销毁,但是通过嵌套一个函数B,B函数中调用fun, 又返回B的函数名 这个操作,就可以保存这个变量在函数B中,所以我们可以通过调用B继续调用到fun,这个就是闭包的作用。
在B函数当中,我们可以调用传入的参数fun。达到扩展和改变这个参数的目的,一般参数是普通变量我们把这个叫闭包,但是当这个参数是函数的时候,就形成了装饰器的概念 .
def A(fun):
def B():
print("---------加入装饰内容1--------------")
fun()
print("---------加入装饰内容2--------------")
return B
三、python装饰器的格式 是什么样
python 中我们是用@函数名 来表示装饰器。放在哪个函数上面,就是装饰哪个函数,下面的就是表示装饰器 A, 装饰函数demo。
而这个装饰的结果也等价于 A(demo),也就是把函数demo,当成参数,传入函数A()里,返回一个新的函数,新的函数名也叫demo 公式表达就是demo= A(demo)。
def A(fun):
def B():
print("---------加入装饰内容1--------------")
fun()
print("---------加入装饰内容2--------------")
return B
#这个装饰就于demo= A(demo)
@A
def demo():
print("hello world")
demo()
四、python实现简单的装饰器
4.1实现无参数的装饰器
def A(fun):
def B():
print("---------加入装饰内容1--------------")
fun()
print("---------加入装饰内容2--------------")
return B
#这个装饰就于demo= A(demo)
@A
def demo():
print("hello world")
demo()
这样每次调用demo函数可以加上打印,而不改变demo本身,这些打印的地方,完全可以替换成我们需要的前置操作和后置操作。
4.1实现有参数的装饰器
def A(fun):
def B(x, y):
print("---------加入装饰内容1--------------")
fun(x, y)
print("---------加入装饰内容2--------------")
return B
# 这个装饰就于demo= A(demo)
@A
def demo(x, y):
print('x+y=', x + y)
demo(1,2)
有参数的装饰器有点不同,我们要明确,demo有两个参数,我们用A去装饰这个demo的时候,等价于 demo= A(demo),所以这个A只能传入一个参数,就是demo这个函数的名字。函数被装饰过后,就是函数B了,所以直接拿出函数B出来看。
def B():
print("---------加入装饰内容1--------------")
fun(x, y)
print("---------加入装饰内容2--------------")
因为fun函数需要两个参数,所以函数 B中需要传入两个参数变量即
def B(x,y): #不同点在于B要接受传入的参数个数
print("---------加入装饰内容1--------------")
fun(x, y)
print("---------加入装饰内容2--------------")
最终就是开始展示的代码。
def A(fun):
def B(x, y):
print("---------加入装饰内容1--------------")
fun(x, y)
print("---------加入装饰内容2--------------")
return B
# 这个装饰就于demo= A(demo)
@A
def demo(x, y):
print('x+y=', x + y)
demo(1,2)
4.2实现有参数的装饰器进阶版,参数不定长
之前我们说的是参数有限,如果参数是不定长的参数呢,那么效果就是下面的,通过解包操作来看解析出个数
def B(*args, **kwargs): #不同点在于B要接受传入的参数个数
print("---------加入装饰内容1--------------")
fun(*args, **kwargs)
print("---------加入装饰内容2--------------")
最终于代码是
def A(fun):
def B(*args, **kwargs):
print("---------加入装饰内容1--------------")
fun(*args)
print("---------加入装饰内容2--------------")
return B
# 这个装饰就于demo= A(demo)
@A
def demo(x, y):
print('x+y=', x + y)
demo(1,2)
4.2实现有 return的关键字的函数的装饰
def A(fun):
def B():
print("---------加入装饰内容1--------------")
fun()
print("---------加入装饰内容2--------------")
return B
# 这个装饰就等于demo= A(demo)
@A
def demo():
return 'hello world'
print(demo())
当被装饰的函数里有通过return 返回一个值的时候,这个返回的值给到的是当前的函数,比如上面的demo函数,通过return返回,返回值给到的是demo这个函数本身, 在被装饰器A装饰的时候,fun 函数的 return的值没有返回给B。 返回值给到的是fun.
所以要把fun(), 加个return,返回给函数B 这样才能打印出来
def A(fun):
def B():
print("---------加入装饰内容1--------------")
result = (fun())
return result #和之前的区别在这里 ,加了一个return函数。
print("---------加入装饰内容2--------------")
return B
# 这个装饰就于demo= A(demo)
@A
def demo():
return 'hello world'
print(demo())
4.3 多个装饰器对同一个函数装饰
当多个装饰器对一个函数进行装饰的时候,如下面所示
def A(fun):
def B():
print("---------加入装饰内容1--------------")
fun()
print("---------加入装饰内容2--------------")
return B
def X(fun):
def Y():
print("---------加入装饰内容3--------------")
fun()
print("---------加入装饰内容4--------------")
return Y
# 这个装饰就于demo= X( A(demo))
@X
@A
def demo():
print('hello world')
demo()
当被多个装饰器装饰的时候,比如上面的demo,就等价于 demo= X( A(demo)) 和数学中函数一样,我们可以从内到外一步步解析或者从外到内一步步解析。如果从外到内来说,先把A(demo) 当成一个整体,就可以看出打印的顺序时先打印装饰内容3,再运行fun,这个fun就是A(demo),再打印装饰内容4.而这个fun 里,先打印装饰内容1,再运行这个函数的fun(),再打印装饰内容2.