Python装饰器
一、简介
1、开放封闭原则
在一个软件上线之后,就应当遵循开放封闭原则,即对修改原代码是封闭的,对功能的扩展是开放的,也就是在不修改一个源代码以及不改变调用的方式的前提下,为其加上新功能
2、什么是装饰器
装饰器就是在不修改被装饰对象源代码与调用方式的前提下,为被装饰对象添加新功能的工具。
装饰器是函数,被装饰的对象也是函数,装饰器与我们所了解闭包函数的逻辑相同
装饰器的原则组成:
< 函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器 >
这个式子是贯穿装饰器的灵魂所在!
二、无参装饰器
2.1、无参装饰器模板
def outer(func): # 最原始的被装饰对象
def inner(*args,**kwargs):
res=func(*args,**kwargs) # 调用被装饰对象
return res
return inner
2.2、无参装饰器应用实例:
有代码如下:
import time
def index():
time.sleep(1)
print('welcome to index page')
return 122
很显然,我们可以看出这段代码肯定是等待1秒后打印'welcome to index page'
我们现在要在不改变源代码和调用方式的前提给程序添加统计时间的功能。
之前学习函数我们肯定也有了解,我们定义变量或者函数所对应的的值就会存到内存里面,我们如果想要调用这个变量或者函数的话就需要用到变量名或者函数名,一个名字对应的就是一个内存地址。我们调用名字就是在调用这个地址里面的内容。
高阶函数==>详解
高阶函数可以有两种形式:一种是把一个函数名当做实参传给另外一个函数(“实参高阶函数”),另一种是返回值中包含函数名(“返回值高阶函数”)
如果可以把函数名当做实参,那么也就是说可以把函数传递到另一个函数,然后在另一个函数里面做一些操作,根据这些分析来看,就满足了不修改源代码而增加功能。
但是我们还得满足不修改调用方式的条件,就是我们最后的执行代码也一定得是index(),这就用到第二种高阶函数,即返回值中包含函数名
但是装饰器真正的核心是闭包函数
另外还有一个需要注意的点,函数只能调用和它同级别以及上级的变量或函数。也就是说:里面的能调用和它缩进一样的和他外部的,而内部的是无法调用的。
现在我们可以满足那两个需求给函数添加功能
def timmer(func):
#func=最原始的home
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs) #调用最原始的home
stop_time=time.time()
print(stop_time-start_time)
return res
return wrapper
index=timmer(index) #① 新的index=wrapper
index() # ②
在①处将函数名index作为参数传给了timmer(),所以现在timmer(func)=timmer(index),在接下来又在timmer函数内部定义了wrapper()函数,但是并未调用,而是在最后作为timmer函数的返回值,返回了wrapper函数的内存地址,
所以说timmer()得到的就是wrapper函数的地址,然后再把wrapper赋值给index,那么现在的index函数就不是原来的index了,而是经过timmer装饰器内部一系列的包装,返回的wrapper函数,在②处调用实际上是wrapper()。这段代码在本质是修改了调用函数,但是并没有修改原来的代码和原来的调用方式,而且增加了功能。
根据以上分析,装饰器在装饰的时候,需要在每个函数前面加上:
index = timmer(index)
相对来说比较麻烦,所以python为我们提供了一种简便快捷的方法,即语法糖 “@”
@timmer
这两句是等价的,只要在函数前加上这句,就可以实现装饰作用。
import time
def timmer(func):
def wrapper():
start_time=time.time()
func()
stop_time=time.time()
print(stop_time-start_time)return wrapper
@timmer #index=timmer(index)
def index():
time.sleep(1)
print('welcome to index page')
return 122
以上内容都是基于被装饰函数没有参数传入,也没有返回值才会实现,但是往往一个函数是肯定会有参数的,而上述函数如果加上参数是肯定会报错的,因为index在调用的时候缺少一个位置参数,这个时候为了是程序更加具有扩展性,就需要为装饰器加上可变参数*args和**kwargs。
import time
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print(stop_time-start_time)
return res
return wrapper
@timmer #index=timmer(index)
def index():
time.sleep(1)
print('welcome to index page')
return 122
我们通过“@”语法糖来调用装饰器的时候,需要注意一点,我们使用这个功能需要将“@+装饰器”在被装饰对象的正上方独占一行。
2.3、迭加多个装饰器
在实际的工作当中,我们可能会遇到需要不止添加加一个新功能的对象,这就需要我们用到多个装饰器进行迭加使用
import time
current_user={
'username':None,
# 'login_time':None
}
def auth(func):
# func=index
def wrapper(*args,**kwargs):
if current_user['username']:
print('已经登陆过了')
res=func(*args,**kwargs)
return res
uname=input('用户名>>: ').strip()
pwd=input('密码>>: ').strip()
if uname == 'egon' and pwd == '123':
print('登陆成功')
current_user['username']=uname
res=func(*args,**kwargs)
return res
else:
print('用户名或密码错误')
return wrapper
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print(stop_time-start_time)
return res
return wrapper
@auth
@timmer
def index():
time.sleep(1)
print('welcome to index page')
return 122
index()
# 使用这种方式我们需要注意两个装饰器的先后顺序,在这个例子当中如果将“@timmer”放在“@anth”前面,那么“timmer”装饰器计算的就是auth+index的执行时间
从上面的代码我们可以看到函数装饰器的迭加使用,他们之间的执行时从上到下的,但是迭加的两个装饰器之间是离函数近的先装饰。
# 定义装饰器1
def set_log(func):
print("__开始装饰set_log__")
def call_func():
print("__set_log__")
func()
return call_func
# 定义装饰器2
def set_func(func):
print("__开始装饰set_func__")
def call_func():
print("__set_func__")
func()
return call_func
# 定义函数,并添加装饰器
@set_log # 等价于 func1=set-log(func1)
@set_func # 等价于 func1=set_func(func1)
def func1():
print("__func__")
func1()
# 执行结果:
__开始装饰set_func__
__开始装饰set_log__
__set_log__
__set_func__
__func__
示例
三、有参装饰器
一个装饰器,对不同的函数有不同的装饰。那么就需要知道对哪个函数采取哪种装饰。因此,就需要装饰器带一个参数来标记一下。我们以往的装饰器,都是传递函数名字进去,而这次,多了一个参数,所以我们就需要再加一层函数来接受参数,根据嵌套函数的概念,要想执行内函数,就要先执行外函数,才能调用到内函数。
import time
current_user={
'username':None,
# 'login_time':None
}
def auth(engine):
# engine='file'
def auth2(func):
# func=index
def wrapper(*args,**kwargs):
if engine == 'file':
if current_user['username']:
print('已经登陆过了')
res=func(*args,**kwargs)
return res
uname=input('用户名>>: ').strip()
pwd=input('密码>>: ').strip()
if uname == 'egon' and pwd == '123':
print('登陆成功')
current_user['username']=uname
res=func(*args,**kwargs)
return res
else:
print('用户名或密码错误')
elif engine == 'mysql':
print('基于MyQL的认证')
elif engine == 'ldap':
print('基于LDAP的认证')
return wrapper
return auth2
@auth('ldap') #@auth2 #index=auth2(index) #index=wrapper
def index():
time.sleep(1)
print('welcome to index page')
return 122
index() # wrapper()
四、装饰器补充:wraps
其实在我们初学装饰器的时候不会注意到这个问题,我们使用装饰器装饰过得函数其实已经不是之前的那个函数了,我们通过 " __doc__ " 方法是没办法调用查看我们被装饰函数的注释内容,所以说我们会为了消除这个副作用,在实现之前加上@wraps,这样就可以保留函数名和docstring。
from functools import wraps
def deco(func):
@wraps(func) #加在最内层函数正上方
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
return wrapper
@deco
def index():
'''哈哈哈哈'''
print('from index')
print(index.__doc__)
五、装饰器应用场景
- 认证,权限的校验
- 函数的事务处理,要么一起成功,要么一起失败
- 插入日志,作为函数的运行日志
- 缓存,实现缓存处理
- 框架的路由传参