python之装饰器

1. 什么是装饰器

知乎大佬如是说:
内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
装饰器本质上是Python函数,可以为已存在的对象添加额外的功能,同时装饰器还可以抽离出与函数无关的重用代码。具体应用场景如:插入日志、性能测试、事务处理、缓存、权限校验等。
换言之
装饰器不能影响原函数的功能,装饰器是独立出来的函数。谁调用它,谁就可以使用它的功能。
2.举个栗子
add的功能是计算x和y的值,我们称作功能函数。
logger的作业是在执行add函数的同时再打印了其他的信息,这部分的作为add的功能增强,我们称为装饰。
在logger里我们可以加入其他类似的功能函数,也能包装它,可以进行复用。
1.引子
#功能函数 def add(x,y): return x+y#装饰函数 def logger(fn): print('frist') x = fn(4,5) print('second') return x print(logger(add))#把函数add传给logger,return x+y #print('frist') #print('secend') #x = fn(4,5)==> x = 4 y= 5x= 4+5 = 9 #return 9

frist second 9

2.提取参数 x,y的参数都放在logger函数内部了,影响函数的灵活性,此处我们可以提取出来。
def add(x,y): return x + y def logger(fn,*args,**kwargs): print('frist') x = fn(*args,**kwargs) print('second') return xprint(logger(add,1,y=11))

frist second 12

3.柯里化
def add(x,y): return x + y def logger(fn): def wrapper(*args,**kwargs): print('begin') x = fn(*args,**kwargs) print('end') return x return wrapperprint(logger(add)(5,y=11))

begin end 16

懵逼ing
以下为个人理解,左边为非柯里化函数,右边是柯里化函数。
python之装饰器
文章图片

前面说过柯里化的定义,本来可以一次传入两个参数,柯里化之后。只需要传入一个函数了。。
左边传入add 和 两个参数。
右边的logger(add)是一个函数,只需要传入两个参数。logger(add)是个整体,结合成一个函数。当然这样写,我们看函数主题的部分也是不一样的。
函数的基础中说过,函数的传参必须和函数参数的定义一致。重点分析右边函数(柯里化)。
参数部分:参数传入的方式,logger函数需要传入个fn,fu的返回值是wrapper函数,wrapper函数的参数是(*args,**kwargs)所以此次就需要分两次传入参数。
第一次传入fn,再次传入wrapper函数需要的参数。所以就出现了最下边的调用方式。
print(logger(add)(5,y=50))。
返回值部分:右侧的logger函数是个嵌套函数,logger的返回值是wrapper,内层的wrapper函数返回值是x,x = fn(*args,**kwargs)。fn函数是最后调用时候传入的add函数。
懵逼 X 2。。。。
def add(x,y): return x + ydef logger(fn,*args,**kwargs):def logger(fn):#参数剥离 def newfunction(*args,**kwargs):#新定义一个函数,logger函数返回也是这个函数名字 print('frist')print('frist') x = fn(*args,**kwargs)== >x = fn(*args,**kwargs) print('second')print('second') return xreturn x return newfunctionprint(logger(add,1,y=11))print(logger(add)(5,y=11)) #两次传入参数

效果如下:
def add(x,y): return x + ydef logger(fn):#参数剥离 def newfunction(*args,**kwargs):#新定义一个函数,logger函数返回也是这个函数名字print('frist') x = fn(*args,**kwargs) print('second') return xreturn newfunctionprint(logger(add)(5,y=11)) #两次传入参数

frist second 16

继续懵逼的话就这样用吧。。。用多了就悟道了。。
4.装饰器语法糖
#再次变形。。。 def add(x,y): return x + ydef logger(fn): def wrapper(*args,**kwargs): print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper##调用方法1: print(logger(add)(x=1111,y=1))##调用方法2: add = logger(add) print(add(x=11,y=3))##调用方法3: python给我们的语法糖 @logger # 说明下边的函数,add 其实是 add = logger(add) def add(x,y): return x + yprint(add(45,40))

begin end 1112 begin end 14 begin end 85

3.复杂的栗子
import datetime import time def logger(fn): def warp(*arges,**kwarges): print("arges={},kwarges={}".format(arges,kwarges))#打印函数的两个参数 start = datetime.datetime.now()#获取函数运行的开始时间 ret = fn(*arges,**kwarges)#传入两个参数,调用add函数此处有个return的值,需要一层一层的返回出去duratime = datetime.datetime.now() - start#获得函数的运行时间 print("function {} took {}s".format(fn.__name__,duratime.total_seconds()))#打印函数的运行时间return ret#返回fn的结果 ,fn = x+y ==> 返回x+y的值。x = 4 y= 11 ==> return 11 return warp#返回warp的 return ==> ret 的return ==> return 11 函数的最终结果为11@logger def add(x,y): print("oooooook") time.sleep(1.5) return x+yprint(add(4,y=11))#如果充分理解了每个小部件,这个简单的完整版本也是很好理解的了。 #1,logger是个装饰器,而且使用了柯里化技术 #2,add 传参给logger的fn 形参,add(4,y=5)的两个参数传入给warp函数的两个形参 # #

arges=(4,),kwarges={'y': 11} oooooook function add took 1.5017s 15

再次翻译
import datetime import time #####################################装饰开始############################################ def logger(fn):#拿到函数名称 def warp(*arges,**kwarges):#拿到函数带过来的参数开始装饰 print("arges={},kwarges={}".format(arges,kwarges))#来试试打印两个参数 start = datetime.datetime.now()# ret = fn(*arges,**kwarges)# 此处调用add函数。开始执行函数,发现return语句。。ret的结果就是return。 duratime = datetime.datetime.now() - start# print("function {} took {}s".format(fn.__name__,duratime.total_seconds()))return ret#加工完成开始返回。warp的返回值是ret ,ret的返回值是 add函数的执行结果(原函数的功能完整的保留了) return warp# logger的返回结果是warp,warp的返回值是ret ,ret的返回值是 add函数的执行结果(原函数的功能完整的保留了) #####################################装饰完成############################################@logger#装饰工厂 ######add是需要被装饰的函数,当你有这个想法的事情,其实事情已经开始发生了。 def add(x,y): # 此时add = logger(add)此处前面的@logger标记就是想要让logger装饰器像一个工厂一样对add函数进行加工。 print("oooooook") time.sleep(1.5) return x+yprint(add(4,y=11))

arges=(4,),kwarges={'y': 11} oooooook function add took 1.501604s 15

4.带参装饰器
1. 文档字符串 我们约定,在python函数的第一行需要对函数进行说明,使用三引号表示。
如果是英文说明,惯例首字母大写,第一行写概述,空一行,第三行写详细描述。
如果函数中有文档字符串,默认会放在函数的doc属性中,可以直接访问。
def add(x,y): """This is a function of addition""" a = x+y return x + yprint("function name is {}\nfunction doc = {}\n\n".format(add.__name__, add.__doc__)) print(help(add))

function name is add function doc = This is a function of additionHelp on function add in module __main__:add(x, y) This is a function of additionNone

2. 前面装饰器的副作用 前面装饰器基本上已经可以完成对函数进行加强的功能了,但是还有些瑕疵。比如原来函数的原属性已经被替换为装饰器的属性了。如下:
def add(x,y): return x + ydef logger(fn): "This is logger doc" def wrapper(*args,**kwargs): "This is wrapper doc" print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper@logger # add = logger(add) def add(x,y): "This is add doc " print("name = {}\ndoc = {}".format(add.__name__,add.__doc__)) return x + yprint(add(45,40))#可以看出来add被装饰出来的函数(新的add)的属性已经全部改变了。

begin name = wrapper doc = This is wrapper doc end 85

3. 解决方案一 三个函数:
  1. copy原函数的属性 copy_properties
  2. 装饰器 logger
  3. 功能函数 add
def copy_properties(src, dst): # 把src的相关属性赋值给dst(fn,wrap) dst.__name__ = src.__name__ dst.__doc__ = src.__doc__def logger(fn): """'This is a function of logger'""" def wrap(*arges,**kwarges): # """'This is a function of wrap'""" print('<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>') x = fn(*arges,**kwarges) #print("name={}\ndoc={}".format(add.__name__,add.__doc__)) print('<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>') return x copy_properties(fn,wrap)#思考1:为什么放在这个位置调用 return wrap@logger def add(x,y): """'This is a function of add'""" print("name={}\ndoc={}".format(add.__name__,add.__doc__)) return x+yprint(add(4,6))

<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<> name=add doc='This is a function of add' <>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<> 10

4. 解决方案二 但凡使用装饰器都会出现属性的这个问题,为什么不把copy_properties也做成装饰器呢?
三个函数:
  1. copy原函数的装饰器 copy_properties1
  2. 装饰器 logger
  3. 功能函数 add
def copy_properties(src, dst): # 把src的相关属性赋值给dst(fn,wrap) dst.__name__ = src.__name__ dst.__doc__ = src.__doc__#利用前面的知识我们可以对copy_properties轻松进行变形 def copy_properties1(src): #把src的相关属性赋值给dst(fn,wrap) def _copy(dst): dst.__name__ = src.__name__ dst.__doc__ = src.__doc__ return dst return _copy

带参装饰器:
def logger(fn): """'This is a function of logger'""" @copy_properties1(fn) #wrap = copy_properties(fn)(wrap) #== > 柯里化 两次传入参数 src = https://www.it610.com/article/fn , dst = wrap 新的wrap函数的属性已经替换为原函数的。def wrap(*arges,**kwarges): #wrap = copy_properties(fn)(wrap)(*arges,**kwarges)"""'This is a function of wrap'""" print('>->->->->->->->->->->->->->->->->->->->->->->->->->') x = fn(*arges,**kwarges) print('<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<') return x return wrap@logger#add =logger(add) def add(x,y): """'This is a function of add'""" print("name={}\ndoc={}".format(add.__name__,add.__doc__)) return x+yprint(add(4,11))

>->->->->->->->->->->->->->->->->->->->->->->->->-> name=add doc='This is a function of add' <-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-< 15

【python之装饰器】更多欢迎访问:http://www.mykernel.cn/

    推荐阅读