举例
def play():
print("小王在玩游戏")
sleep(3)
print("游戏结束")
我想计算play花费的时间
def play():
start = time.time()
print("小王在玩游戏")
sleep(3)
print("游戏结束")
end = time.time()
print("用时{:.2f}".format(end - start))
如果我还想计算其他类似函数的时间怎么办?
我可以把计算函数时间封装成一个函数,解决冗余问题
def cal_time(func):
start = time.time()
func()
end = time.time()
print("用时{:.2f}".format(end-start))
这样解决了冗余问题,但是有个新的问题,我想计算时间就必须调用cal_time方法
有没有方法可以仍然调用play等计算时间需求本身的函数而又可以解决重复冗余问题呢
这就是下面的装饰器
def cal_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print("用时{:.2f}".format(end - start))
return wrapper # 返回内置函数
@cal_time
def play():
print("小王在玩游戏")
sleep(3)
print("游戏结束")
if __name__ == '__main__':
play()
装饰器通过定义内置函数,把需要装饰的函数(play)和一些自由变量(start,end等)绑定在一起,从而实现解决冗余,同时保存原函数调用方法
那么怎么传递参数呢?
def cal_time(func):
def wrapper(*args):
start = time.time()
func(*args)
end = time.time()
print("用时{:.2f}".format(end - start))
return wrapper # 返回内置函数
@cal_time
def play(game):
print("小王在玩游戏 %s" % game)
sleep(3)
print("游戏结束")
if __name__ == '__main__':
play("LOL")
下面是带有 **kwargs参数的装饰器
def cal_time(func):
def wrapper(*args,**kwargs):
start = time.time()
func(*args,**kwargs)
end = time.time()
print("用时{:.2f}".format(end - start))
return wrapper # 返回内置函数
@cal_time
def play(game,name,t):
print("%s 在玩游戏 %s" % (name,game))
sleep(t)
print("游戏结束")
if __name__ == '__main__':
play("LOL","小虎",t=5)
注意:
- args是一个由python位置参数组成的元组,*args则是解压缩元组
- kwargs是由python位置参数组成的字典,可以通过 kwargs.items()遍历
- 如果函数参数(play)只有普通参数(不含有位置参数),则装饰器中不需要由**kwargs,否则,必须有参数**kwargs
当函数有返回值时,wrapper必须返回相应函数的返回值,否则返回None
def cal_time(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("用时{:.2f}".format(end - start))
return res # 返回值
return wrapper # 返回内置函数
@cal_time
def play(game, name, t):
print("%s 在玩游戏 %s" % (name, game))
sleep(t)
print("游戏结束")
return name # 返回值
if __name__ == '__main__':
username = play("LOL", "小虎", t=5)
print(username)
举例总结:仔细观察play函数的执行流程就会发现,其实就是执行内部的wrapper函数,无非就是(1)wrapper内部可以扩增一些其他自由变量(甚至函数)(2)内部的func可以解决函数重复冗余问题,这个func在此处指的就是play函数,你可以把它替换成任意的函数。
下面是另一个例子
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('args = {}'.format(*args))
return func(*args, **kwargs)
return wrapper
@log
def test(p):
print(test.__name__ + " param: " + p)
if __name__ == '__main__':
test("I'm a param")
装饰器在使用时,用了@语法,让人有些困扰。其实,装饰器只是个方法,与下面的调用方式没有区别:
def test(p):
print(test.__name__ + " param: " + p)
wrapper = log(test)
wrapper("I'm a param")
@语法只是将函数传入装饰器函数,并无神奇之处。值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。可以尝试去除@functools.wraps(func),你会发现test.__name__的输出变成了wrapper。