一、为何要用装饰器
软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
二、什么是装饰器
’装饰’代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
综上所述:函数装饰器就是不修改函数源代码和调用方式的前提下,为函数添加新的功能
三、装饰器功能的实现
下面以具体的代码以及注释为例开展讲解
def index(a,b): time.sleep(5) print('任巍峰大帅哥') print(a,b) return 'index的返回值' # 现在有一个需求,统计index执行花了多少时间 # 方案一:直接在函数里面加代码 import time def index(a,b): start_time = time.time() time.sleep(3) print('任巍峰大帅哥') print(a,b) stop_time = time.time() print(stop_time - start_time) return 'index的返回值' index() #这样子也实现了需求,但是,代码应该遵循开放关闭原则,即对函数的拓展功能开放,对修改函数的源代码关闭 # 综上所述,如何实现不修改函数源代码又给函数增加新的功能 # 方案二:那我在调用函数的时候,在调用阶段给函数加功能,如下所示: import time def index(a,b): time.sleep(3) print('任巍峰大帅哥') print(a,b) return 'index的返回值' # start_time = time.time() index() stop_time = time.time() print(stop_time - start_time) # 综上所述,方案二实现了我在不修改函数源代码的前提下,为函数增加新功能的要求,但是函数总不能只在一个地方调用把,难道每次调用函数都要 # 重复书写这样子的操作才能为函数增加这个统计时间的新功能? # 方案三:基于方案二提出来的问题,那么我们自然会想到,把增加新功能的这几行代码,打包成一个新函数,那么以后调用不就可以直接拿来用了吗 import time def index(a,b): time.sleep(3) print('任巍峰大帅哥') print(a,b) return 'index的返回值' # def wapper(): start_time = time.time() index(111,222) stop_time = time.time() print(stop_time - start_time) wapper() #方案三优化1 现在我们发现了一个问题,那就是index函数的参数这样子写是死的,这样子只能为为函数index的参数为(a=111,b=222)这种情况实现增加功能 # 那么,我们自然想到,把参数写活,优化方法就用到了我们的闭包函数 import time def index(a, b): time.sleep(3) print('任巍峰大帅哥') print(a, b) return 'index的返回值' def wapper(*args, **kwargs): #此处用可变长度的位置参数和可变长度的关键字参数,是因为这样子可以任意组合,无论参数是多是少,都可以接收 start_time = time.time() index(*args, **kwargs) stop_time = time.time() print(stop_time - start_time) wapper(6666,777) # 方案三优化2现在又有一个问题,我这样子写只能为index这一个函数增加统计时间的功能,那么其他的函数如home函数也想用,怎么办,,那么自然想到 # 把函数名写成参数的形式,不就可以解决了吗,那么再将现在的wapper函数打包成一个函数,组成闭包函数 # 那么不就每个函数都可以调用了吗 import time def index(a, b): time.sleep(3) print('任巍峰大帅哥') print(a, b) return 'index的返回值' def home(x,y): time.sleep(3) print('home里的任巍峰大帅哥') print(x,y) return 'home的返回值' def outer(func): def wapper(*args, **kwargs): start_time = time.time() func(*args, **kwargs) stop_time = time.time() print(stop_time - start_time) return wapper # #我们想用的是里面的函数wapper,直接调用outer,是不会调起来wapper的,所以,我们将outer函数的返回值设定成 # #wapper,并将其赋值给一个变量名f,以后通过调用f就可以调用wapper f = outer(index) f(1,2) e = outer(home) e('任巍峰','大帅哥') # #综上所述,我们基本完成了我们当初的设想,即不改变函数的源代码,为函数增加新的功能,而且是给不止一个函数增加功能 # #但是我们还没有满足函数的调用方式一样这个要求,不能每次增加一个功能,就让用户记一个新的操作命令 # #这就要用到变量的概念 # i = 1 #此处,在内存中开辟一块地址用来保存数据1,并将该内存的内存地址赋值给变量名i # i = i+1 #变量的公式都是从右向左看的,i+1,此时i对应的是数据1的内存地址,然后i+1 就是1+1= 2 # #然后从右向左看,就是将数据2保存的内存地址重新赋值给变量名i,同时i与以前的数据1的绑定结束 # 要想达到调用方式不变的目的,只需要将f改成对应的函数名,原理同i= i+1 # f = outer(index)>>>>>>index = outer(index) # f(1,2)>>>>>index(1,2) # e = outer(home)>>>>>>home = outer(home) # e('任巍峰','大帅哥')>>>>home('任巍峰','大帅哥') #方案4差一步(装饰器修复技术)成最终版的装饰器 #至此函数的装饰器基本完成,几乎可以以假乱真,唯一的破绽就是函数的返回值,那么如何让函数的返回值也一样呢 #首先明确一下函数的返回值怎么返回 # 变量名 = 函数名() # 函数名加()就会调用函数,执行函数的子代码,并在执行完成以后,将函数的返回值赋值给变量名 # 因此我们将装饰器做如下修改 def outer(func): def wapper(*args, **kwargs): start_time = time.time() res =func(*args, **kwargs) #此处是调用函数并将返回值赋值给变量名res stop_time = time.time() print(stop_time - start_time) return res return wapper # 至此函数的装饰器足以以假乱真,既没有修改函数的源代码,也没有改变函数的调用方式
四、装饰器修复技术
# 综上所述,我们的装饰器基本可以以假乱真,但是如果懂技术的人,通过查看我们的函数名所对应的内存地址,还是可以看出来,我们的函数和以前的内存地址和所属对象是不一样的
import time def index(a,b): time.sleep(5) print('任巍峰大帅哥') print(a,b) return 'index的返回值' print(index) #<function index at 0x00000146A1FB70D0> def outer(fuction): def wapper(*args,**kwargs): start_time = time.time() res = fuction(*args,**kwargs) stop_time = time.time() print(stop_time - start_time) return res return wapper index = outer(index) index(1,2) print(index) #<function outer.<locals>.wapper at 0x00000146A1FB7160>
# #我们发现,前后打印函数的内存地址还是不一样,那就需要用到装饰器修复技术,即通过引用别人写好的装饰器来修复最后一个问题 # print(index) #<function index at 0x00000146A1FB70D0> # print(index) #<function outer.<locals>.wapper at 0x00000146A1FB7160>
引用别人写好的装饰器
import time from functools import wraps def index(a,b): time.sleep(5) print('任巍峰大帅哥') print(a,b) return 'index的返回值' print(index) #<function index at 0x00000231AFD860D0> def outer(fuction): @wraps(fuction) def wapper(*args,**kwargs): start_time = time.time() res = fuction(*args,**kwargs) stop_time = time.time() print(stop_time - start_time) return res return wapper index = outer(index) index(1,2) print(index) #<function index at 0x00000231AFE47820>
五、语法糖
# 经过上文所述,我们发现,在函数调用前,加入一行代码: # # 被装饰函数名=装饰器(被装饰函数名)>>>index = outer(index) # # 函数名()>>>>>>>>>>>>>>>>>>>>>>>>>index(1,2) # # 综上所述即可实现不改变函数源代码也不改变函数调用方式,就可以为函数增加新功能的做法,但是美中不足的是有稍许瑕疵,就是每次调用前需要加代码被装饰函数名=装饰器(被装饰函数名)>>>index = outer(index),这在某种程度也是改变了调用方式,所以引入语法糖的概念 # # 所谓语法糖就是在函数定义阶段,在函数的头顶顶个书写@装饰器名称,例如@outer # # 至此,以后函数调用就跟未修改前一模一样,引入装饰器修复技术以后,通过前后打印函数的内存地址也看不出不一样了
六、装饰器语法总结
from functools import wraps(红色语句是引入修复技术的类和别人写好的装饰器,如果不注重未修复的一点小瑕疵,也可以不写) def 装饰器名字(参数,用来接收被装饰函数的名字): @wraps(fuction) def 内层函数名字(*args, **kwargs): '函数被调用之前需要添加的功能' res=func(*args, **kwargs) # 真正的函数执行,调用函数并接收返回值 '函数被调用之后需要添加的功能' return res return 内层函数名字 函数名 = 装饰器名字(函数名)>>>>>>>>>>>>>等价与在函数定义时在函数头顶写的@装饰器名字 | | | 定义函数 调用函数,函数名() | 调用函数,函数名()