通俗易懂的理解 python 装饰器

举例
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。

参考

python关键字参数
python装饰器

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值