參考一下 www.bilibili.com/video/BV1Sp…
装饰器的样子
先直观的感受下装饰器底层函数的样子
无参装饰器底层的函数
def outter(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
其中 func:被装饰的原函数
wrapper:包裹着被装饰的原函数函数
有参装饰器底层的函数,有参装饰器的函数是支持传递额外的参数param的
def 有参装饰器( param ):
def outter(func):
def wrapper(*args,**kwargs):
print(param)
res = func(*args,**kwargs)
return res
return wrapper
return outter
其中 func:被装饰的原函数
wrapper:包裹着被装饰的原函数函数
装饰器概念
加了装饰器 对使用层面是无感知的,跟原来调用方式一样 wrapper函数就是在原有函数的基础上增加一些功能,
闭包
包:要访问一个变量,这个变量不是从自己那里来,而是从外面的包袱里去找 怎么把wrapper函数变成闭包呢 ,就是把wrapper缩进到一个函数(outter)内部,wrapper内要访问一个变量x,这个变量x还不是wrapper自己函数内部定义的,通过outter将变量x和wrapper包进去,同时返回wrapper,在包之前wrapper 是归属于全局名称空间的,包之后wrapper变成了函数内部,此时wrapper外部包了一圈outter的(作用域或者名称空间)
总结:经过一番操作wraaper函数原来外面包的是全局,现在变成outter,因为outter调用之后,原本outter名称空间是需要销毁的,但是将内部的函数返回给了全局名称空间f,所以即使outter完之后名称空间也不会被回收,因为还有变量引用
当函数内的名字被其他作用域引用的时候,这个函数的名称空间就不会被释放
闭包有啥用呢
通过闭包的方式为函数体传递值,把函数体需要的参数通过闭包的形式报给它
第一步:將wrapper需要的变量定义在外面,同时返回wrapper,返回wraaper的意义:因为后面需要将wraaper包裹在一个函数作用域内部,为了在原来的作用域还能访问到这个函数,所以必须返回。
第二步:包裹一个outter函数,通过outter函数的参数将wrapper需要的变量包裹进去
第三步:调用outter,将参数包给wrapper,同时拿到了当初指向wrapper的地址,wrapper现在外面包裹的就只有outter,通过调用outter传递不同的参数,来实现投食给wrapper
什么是装饰器呢
装饰器指的是定义一个函数(类也行),该函数是用来为其他函数添加额外的功能
开放封闭原则:不修改被装饰对象源代码以及不改变调用方式的前提下,为其增加功能
装饰器实现步骤
前提:為index增加時間记录功能,但是如下方式修改了index源码
新功能加在外面 ,但是在每個调用处都需要写重复代码,代码冗余
将index函数通过wrapper包上之后,index的参数就需要通过wrapper传递进来,但是如果采用下图的这种方式,将来如果index函数升级,比如增加了一个新参数c,那么wrapper函数也需要改造,
所以wrapper函数在定义时将参数设置为 *args **kwargs 这样就可以将参数原封不动的传递给index
wrapper函数的参数很强大,可以接受任意参数,但是被index限制了,只能传递符合index参数规则的参数。
wrapper是为了被装饰对象添加功能的,但是现在按照上面的写法只能装饰index函数,所以为了把被装饰对象写活,需要把被装饰的函数传递进来,这就是上面提到的闭包场景,需要一个参数,但是不能通过参数的方式传递给wrapper,只能在wrapper外面包一层的方式传递进来
装饰器实现思路
偷梁换柱的思想:将原来函数名指向的内存地址 偷梁换柱成装饰器包裹的内部函数地址,内部wrapper函数地址,所以wrapper函数必须要做的跟原函数一样才行,为此要求wrapper函数 : 参数: 可以将wrapper函数的参数原封不动的传递给原封不动 返回值: wrapper函数的参数的返回值 就是原来函数的返回值
以后实际调用的是wrapper函数 在wrapper函数内部调用的是原函数,以实现通过wrapper来扩展原函数的功能,因为wrapper可以在调用前调用后植入自己的扩展功能代码
如何做的一模一样和原来的函数 包括函数的元信息 wraps
怎么做:就是把原函数的一些属性都赋值给wrapper函数,包括原函数的__name__,__doc__等
def outter(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
上面的是我们自己手动实现的方式,比较麻烦,会遗漏 那有没有一种相对简单的方式将原函数的属性赋值给wrapper函数
from functools import wraps
def outter(func):
@wraps(func)
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
python中装饰器语法糖
在没有装饰器之前 怎么才能把index函数装饰一下呢 需要手动的调用outer函数 将被装饰的函数传递进去,偷梁换柱一下 返回一个新的wrapper函数 index = outer(index),得到的结果重新覆盖给原来的函数变量index
有了python提供的语法糖: python解释器在加载到如下代码的时候 会立即将index函数作为变量传递给outter变量,也就是等价于 index=outter(index) 也就是outter必须是一个可以callable的对象,函数和类都可以callable
但是如果是有参装饰器 @outter2(1,2,3) 解释器在加载到@outter2(1,2,3),会先调用outter2(1,2,3),所以outter2(1,2,3)的返回值必须是一个可以调用的对象,因为解释器会用这个返回值将被装饰的函数传递进去,也就是等价于 res = outter2(1,2,3) index=res(index)
@xxx 就是在调用xxx xxx(正下方的函数)
@outter
def index():
print(1234)
无参装饰器的模板
先定义一个wrapper函数:一方面需要调用原来的功能func 另一方面要扩展原来的功能
然後这个wrapper是在以后调用的,所以缩进到outter里面形成闭包。
有参装饰器的模板
思考一下 无参装饰器中outter函数可以接收除了func以外的参数吗
def outter(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
其中 func:被装饰的原函数
wrapper:包裹着被装饰的原函数函数
答案是不允许的,这是由@语法糖限制引起的,解析器在看到@outter 后会立即被调用,将被装饰的函数传递进去 wrapper = outter(被装饰的函数),如果outter定义了多个参数,那么调用的时候就会报错,所以无参装饰器中 outter函数的唯一参数必须是func --- 即被装饰的原函数
所以想要语法糖支持带参数的写法,@outter(xxx),解释器会先去执行outter函数,那么此时就相当于是 '@outter函数返回值' 所以就要求outter函数返回值是一个能够接收func,并且返回值是wrapper的函数
即 我们要求outter函数的返回值是一个如下函数 def decorate(func)--> wrapper
所以有参装饰器要解决的问题是什么呢:就是解决要给wrapper函数传递额外参数的问题,有人可能会有疑问wrapper参数不是定义成*args,**kwargs,可以接收任意参数吗,其实不是这样的,wrapper函数的参数设计成这样是因为它需要将参数完整的传递给原函数。用户在使用被装饰的函数时,实际使用的是wrapper函数, wraaper函数的职责除了为原函数拓展功能,就是模拟原来的函数功能,要求用户使用wrapper函数的时候就和使用原函数是一样的,所以wraaper函数的参数设计成什么样取决于原函数的参数 ,那既然无法给wrapper函数传递额外的参数,那就选择通过闭包的方式将参数包给它
一个函数被多个装饰器装饰的执行流程
@auth2
@auth1
def index():
pass
会先执行auth1 在执行auth2,靠近原函数的地方先执行
也就是第一步 auth1中包裹的函数 = auth1(index) , 返回的是auth1中包裹的函数
第二步 auth2中包裹的函数 = auth2(auth1中包裹的函数) 返回的是auth2中包裹的函数
那么用户在调用index的时候,最先执行的就是auth2中包裹的函数,然后执行auth1中包裹的函数