说透python装饰器

图片来自pixabay

装饰器的执行逻辑

import functools

def wrapper(func):
  print("This is wrapper")   1⃣️
  
  @functools.wraps(func)
  def inner(*args,**kwargs):
    print("This is inner")   2⃣️
    return func(*args, **kwargs)
  return inner

@wrapper
def test():
  print("This is test")  3⃣️
  return True

# 因为有 @ 的原因,不增加main方法,
直接运行此文件,会执行wrapper中的代码  1⃣️ ,输出 “This is wrapper”, 其他代码不会执行


if __name__ == '__main__':
  test()
  
“”“ 如果增加了main方法,运行此文件,输出顺序为 1⃣️  2⃣️  3⃣️

多个装饰器的执行顺序

import functools

def wrapper(func):
  print("This is wrapper")   1⃣️
  
  @functools.wraps(func)
  def inner(*args,**kwargs):
    print("This is inner")   2⃣️
    return func(*args, **kwargs)
  return inner

def wrapper2(func):
  print("This is wrapper2")  4⃣️
  
  @functools.wraps(func)
  def inner(*args, **kwargs):
    print("This in wrapper2 inner")  5⃣️
    return func

@wrapper2
@wrapper
def test():
  print("This is test")   3⃣️
  return True
  
“”“
同样,如果不添加main方法,直接运行因为@的存在会执行wrapper 
和wrapper2的部分代码,顺序是先输出 1⃣️ ,后输出 4⃣️ ,且仅输出这些内
容,原则是距离被装饰的函数 test 越近的装饰器越先被执行
”“”

if __name__ == '__main__':
  test()

添加main方法后,运行此文件,仍然是按照距离越近的越先被调用,即先输出1⃣️4⃣️,之后的输出却是执行wrapper2的inner,即输出5⃣️,再执行wrapper的inner,输出2⃣️,最后执行被装饰的函数的逻辑,输出3⃣️。所以最终的输出顺序是1⃣️ 4⃣️5⃣️2⃣️3⃣️

带有参数传递的装饰器

# 场景:在日志输出被装饰函数的执行耗时场景中,可以在inner中调用执行被装饰函数,并在其前和其后分别获取time.time()变量,减法的结果输出到日志。但日志输出通常需要伴随被装饰函数或者api的信息,以确定是哪个函数名或api名耗时,就需要传递参数给装饰器
import time
from functools import wraps

def time_cost(api_name=None):
  def decorator(func):
    @wraps(func)
    def inner(*args,**kwargs):
      start_time = time.time()  			# 前处理逻辑
      result = func(*args,**kwargs)   # 调用被装饰函数
      end_time = time.time()  				# 后处理逻辑
      cost_time = (end_time-start_time)*1000
      print("{} costs time:{:.4f} ms".format(api_name, cost_time))
      return result
    return inner
  return decorator

# 如果要给装饰器传参,需要外层再加一层函数

@time_cost("test_api")
def test():
  pass

使用类定义装饰器

class Decorator:
  def __init__(self, func):  # 初始化传参是 被装饰的函数对象
    self.func = func

  def __call__(self, *args, **kwargs):  # 使用 __call__ 内嵌函数实现
    print("This is __call__")
    return self.func(*args, **kwargs)
  
@Decorator
def test():
  print("This is test")
  
if __name__ == '__main__':
  test()
  
>>>
This is __call__
This is test

# 为什么要使用类定义装饰器,在面对逻辑比较复杂的装饰器定义的时候,
# 使用类,可以降低代码逻辑的复杂度

使用类定义装饰器实现简单的缓存 (主要应用了类成员属性的特性)

import time


class Cache:
  __cache = {}
  
  def __init__(self, func):
    self.func = func
    
  def __call__(self, *args, **kwargs):
    if self.func.__name__ in Cache.__cache:
      print('已经在缓存中,直接从中获取值')
      return Cache.__cache.get(self.func.__name__)
    else:
      print("未在缓存中,直接计算")
      value = self.func(*args, **kwargs)
      Cache.__cache.update({self.func.__name__: value})
      return value
    
@Cache
def test(param):
  time.sleep(5)
  return param

if __name__ == '__main__':
  for i in range(5):
    print(test(i))
    
>>>
未在缓存中,直接计算
0
已经在缓存中,直接从中获取值
0
已经在缓存中,直接从中获取值
0
已经在缓存中,直接从中获取值
0
已经在缓存中,直接从中获取值
0

# 输出的结果,只有第一行结果输出后,等待了5s,其余的内容都是瞬间输出的,
#且添加了缓存之后,如果我们期望每次调用如果缓存中有数据,就获取缓存的数据
#而非再次调用原函数,就可以对目标函数增加缓存装饰器,以达到快速获取数据的效果

总结及注意点:

  • 装饰器的inner函数,通常用 @functools.wraps(func)装饰,以保留被装饰函数的原本的属性,如:不使用functools.wraps(func)装饰,main 中打印 print(test.__name__)输出不是test,而是装饰器函数中的inner的名称。多个装饰器的情况下,会输出最后一个不使用functools装饰的inner。
  • 装饰器的inner函数,返回值为 func(*args,**kwargs) ,是 func()执行结果 而非 func 函数对象,如果仅返回func对象,则被装饰函数将不会被执行,仅执行装饰器和inner的逻辑
  • 多个装饰器,执行逻辑按照先执行距离被装饰函数较近的装饰器,由近及远,然后执行inner的逻辑,由远及近,最后执行被装饰的函数逻辑
  • 如果需要增加被装饰函数执行后的逻辑,可以在 inner 中调用被装饰函数 result=func(*args,**kwargs),在其后增加后处理逻辑,最后return result,此方法常见的是日志输出被装饰函数的运行时间,在函数执行前和结束后分别获取time.time(),执行减法输出在日志

引申:

  • 装饰器传递参数,在外层再包一层函数的操作,其实是python中闭包的使用

  • 闭包,说白了是在一个函数内部定义一个函数,并将此函数对象返回,如:

    def test(*args,**kwargs):
      def inner():
        pass
      return inner
    
    if __name__ == '__main__':
      func = test()
      func()  # 可以直接调用 test 中定义的函数 inner
    

    比较官方的说法: 闭包是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行,这样一个函数称为闭包;
    Python闭包有助于避免使用全局值,并提供某种形式的数据隐藏;
    一个用于理解闭包的例子:

    def test():
      local_list = []
      number = 0
      def inner(num):
        local_list.append(number+num)
        print("local_list:{}, number:{}".format(local_list,number))
      return inner
    
    if __name__ == '__main__':
      func01 = test()
      func01(1)
      func01(2)
      func01(3)
      func02 = test()
      func02(1)
      func01(4)
      func02(2)
      
    >>>
    local_list:[1], number:0
    local_list:[1, 2], number:0  
    # 只要func01对象没有被销毁,其定义的作用域中的变量也就不会被销
    #毁,尽管test方法已经执行完毕,但内部的变量 local_list 会伴随实例
    # func01 的存在而存在
    local_list:[1, 2, 3], number:0
    local_list:[1], number:0
    local_list:[1, 2, 3, 4], number:0
    local_list:[1, 2], number:0
    

    func01 和func02 可以认为是 test 闭包的两个实例;
    实例 func01 有一个与其对应的 local_list (称为自由变量),func02也有一个与其对应的自由变量,两者调用各自的自由变量相互不受影响;
    但是一个实例的自由变量的修改,会传递给下一次闭包的调用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值