装饰器
装饰器
使用目的:在不修改函数源代码的基础上,添加额外的功能,类似打补丁
了解装饰器之前先要清除闭包的概念
闭包
- 引用了外部自由变量的函数
- 自由变量:不在当前函数定义的变量
- 特性:自由变量会和闭包函数同时存在
- 即使程序离开发布作用域,如果闭包仍然可见,绑定变量不会销毁
- 每次运行外部函数都会重新创建闭包
def startAt(x):
def incrementBy(y): # 闭包函数
return x+y # x相对于incrementBy就是外部自由变量
return incrementBy # 返回incrementBy闭包函数的地址
a = startAt(1) # 此时a拿到的是incrementBy的地址,只是一个函数名
a(2) # 实质是调用了incrementBy(2),返回的是x+2,而x在上一句已经赋值为1,所以结果是1+2=3
而装饰器就是把x本来是传值的,改成传函数名,即函数地址
此处为了更好理解一些,把x改成func
import time
def startAt(func): # 接收一个函数地址
def incrementBy(*args,**kwargs): # 设定接收被传进来的func的所有参数
beg = time.time()
res = func(*args,**kwargs) # 即在incrementBy函数中调用mysleep()
print(f'user time:{time.time()-beg}')
return res
return incrementBy # 返回incrementBy函数地址
@startAt
def mysleep():
time.sleep(1)
# a = startAt(mysleep)
# a() 实际newsleep()就是调用的incrementBy()
疑问:明明startAt只是拿到了函数的地址,为什么可以把参数传到incrementBy中?
可以这么理解,函数的形参和函数名是打包在一起的,统一记录在同一个地址中,函数在调用时才会使用形参,所以incrementBy并不知道穿过来的是什么形参,它只是贪婪的全部接收,让func在调用时直接使用就行了
import time
# 使用类装饰器适合给装饰器加参数
# 使用类作为装饰器
class LogTime:
def __init__(self,user_int=False)
self.user_int = user_int
def __call__(self,func): # 类方法
def _log(*args,**kwargs):
beg = time.time()
res = func(*args,**kwargs)
if self.user_int:
print(f'user time:int({time.time()-beg})')
else:
print(f'user time:{time.time()-beg}')
return res
return _log
@LogTime(user_int=False) # 使用括号是因为类需要生成一个实例
def mysleep():
time.sleep(1)
# a = LogTime(user_int=False)
# a()
其实不使用类装饰器也能在装饰器中加参数,但是需要在外层再封装一个工厂函数用来接收实参
比如:
def get_parameter(*args,**kwargs): # 工厂函数,用来接受@get_parameter('index.html/')的'index.html/'
def log_time(func):
def make_decorater():
print(args,kwargs)
print('现在开始装饰')
func()
print('现在结束装饰')
return make_decorater
return log_time
@get_parameter('index.html/')
def test():
print('我是被装饰的函数')
# return num+1
test() # test()=make_decorater()
优点和缺点
- 优点:在不修改源代码的同时增加额外的功能
- 缺点:原函数的名字会被修改
- 避免原函数的名字修改:在内嵌函数上使用@functools.wrap(fn)装饰
测量函数执行时间装饰器
import time
def cal_time(func):
def wrapper(*args,**kwargs):
time1 = time.time()
result = func(*args,**kwargs)
time2 = time.time
print("%s running time: %s" %(func.__name__,time2-time1))
return result
return wrapper