python装饰器详解

背景

python项目开发过程中会遇到比较多的使用装饰器的场景,现将python中的装饰器这个强大的功能总结一下

自定义装饰器

自定义装饰器主要分三种,不带参数的函数装饰器,带参数函数装饰器,以及类装饰器. 下面一一进行讲解

  • 不带参数的函数装饰器
    from functools import wraps
    // 装饰器函数,接受一个参数,参数代表被装饰的函数
    def no_args_decorator(func):
    	@wraps(func)
    	// 内层函数,入参是被装饰函数的全部参数,内部可以在被装饰函数调用前后实现一些逻辑
    	def wrapper(*args, **kwargs):
    		# before
    		print("before")
    		// 是否有返回值取决于被装饰的函数
    		res = func(*args, **kwargs)
    		# after
    		print("after")
    		return res
    	return wrapper
    
    @no_args_decorator
    def test(arg1, arg2):
    	print(arg1, arg2)
    
    if __name__ == '__main__':
    	test1(1, 2)
    
    运行结果
    before
    1 2
    after
    
    总结一下就是两层函数,外层为装饰器名称,只接收一个参数表示被装饰的函数,内层函数参数为被装饰函数的所有入参.
  • 带参数的函数装饰器
    带参数的函数装饰器其实就是在不带参数的装饰器外面再套一层函数定义,用于接收该装饰器的参数
    from functools import wraps
    // 最外层为装饰器名称,参数为装饰器本身所需的参数
    def args_decorator(arg1=0, arg2=1):
    	def inner_decorator(func):
    		@wraps(func)
    		def wrapper(*args, **kwargs):
    			print(f"decorator args: {arg1} {arg2}")
    			res = func(*args, **kwargs)
    			return res
    		return wrapper
    	return inner_decorator
    
    // 装饰器有其默认的参数也需要加括号
    @args_decorator()
    def test(a, b):
    	print(a, b)
    
    if __name__ == '__main__':
    	test(3, 4)
    
    输出结果
    decorator args 0 1
    3 4
    
    带参的装饰器就是三层函数,第一层是装饰器名称,参数是装饰器所需的入参,第二层到第三层本质上就是一个无参的装饰器.
  • 类装饰器
    类装饰器顾名思义是定义一个类来实现装饰器功能,它必须实现__init__方法以及__call__方法,类装饰器 同样可以实现不带参数的函数装饰器以及带参数的函数装饰器效果.
    1. 不带参数的装饰器
    class MyDecorator:
      def __init__(self, func):
        self.func = func
      
      def __call__(self, *args, **kwargs):
        print("before")
        res = self.func(*args, **kwargs)
        print("after")
        return res
    
    @MyDecorator
    def test(a, b):
      print(a, b)
    
    if __name__ == '__main__':
    	test(3,4)
    
    运行结果
    before
    3 4
    after
    
    1. 带参数的装饰器
    class MyArgDecorator:
      def __init__(self, arg1=0, arg2=1):
        self.arg1 = arg1
        self.arg2 = arg2
      
      def __call__(self, func):
        def wrapper(*args, **kwargs):
          print(f"args are {self.arg1} {self.arg2}")
          print("before")
          res = func(*args, **kwargs)
          print("after")
          return res
        return wrapper
    
    # 带参数的修饰器一定要加括号
    @MyArgDecorator()
    def test(a, b):
      print(a, b)
    
    if __name__ == '__main__':
    	test(3, 4)
    
    输出结果
    args are 0 1
    before
    3 4
    
    和函数装饰器调用方式以及功能一致,只是使用类来实现,在自定义装饰器的应用场景下,个人还是更推荐函数装饰器的实现方式.

装饰器链

当多个装饰器作用于同一个函数的时候,遵循一个编程世界中链式(比如web开发中的filter chain)的调用规则, 按照装饰器的定义顺序,由前到后执行.比如下面的例子

def my_decorator1(func):
  def wrapper(*args, **kwargs):
    print("decorator1 before")
    res = func(*args, **kwargs)
    print("decorator1 after")
    return res
  return wrapper

def my_decorator2():
  def inner_decorator(func):
    def wrapper(*args, **kwargs):
      print("decorator2 before")
      res =  func(*args, **kwargs)
      print("decorator2 after")
      return res
    return wrapper
  return inner_decorator

@my_decorator1
@my_decorator2()
def test(a, b):
  print(a, b)

if __name__ == '__main__':
	test(3,4)

运行结果:

decorator1 before
decorator2 before
3 4
decorator2 after
decorator1 after

functools包中的常用装饰器

内置的装饰器通常用于类的定义中,这里不展开讲,functools包中提供了更多的实用装饰器,这里总结一下常见的装饰器使用方法:

  • lru_cache(maxsize=128, typed=False)和 cache. lru_cache实现缓存函数执行结果,可以配置缓存大小,以及配置是否严格的区分入参的数据类型. 而cache可以看做是lru_cache一个特例,其代表没有边界的cache类型.
    @lru_cache
    def test_cache(a, b):
      print("catched function runs")
      return a + b
    
    def test_no_cache(a, b):
      print("normal function runs")
      return a + b
    
    if __name__ == '__main__':
      for i in range(5):
        test_cache(1, 2)
        test_no_cache(1, 2)
    
    运行结果:
    catched function runs
    normal function runs
    normal function runs
    normal function runs
    normal function runs
    normal function runs
    
    可见被lru_cache装饰的函数在相同入参多次调用时只调用了一次,而普通的函数则是调用多次.
  • singledispatch(func) 函数分派实现基于装饰器的多态. 装饰器可以实现根据函数的第一个参数的类型决定具体调度的函数.需要定义一个通用的函数作为default调度函数,同时可以使用此通用函数作为装饰器去注册各种具体的入参类型的调度函数,例子如下:
     # 通用函数,其他注册函数类型都不满足的时候调用
    @singledispatch
    def my_func(data):
      print(f"generic func runs and input data type is {type(data)}")
    
    # 通用函数注册处理int类型入参的函数
    @my_func.register(int)
    def my_func_int(data):
      print(f"input data type is {type(data)}")
    
    # 通用函数注册处理str类型入参的函数
    @my_func.register(str)
    def my_func_str(data):
      print(f"input data type is {type(data)}")
    
    if __name__ == '__main__':
      #入参类型是float,使用默认调度函数
      my_func(23.0)
      #入参类型是int使用注册入参类型为int调度函数
      my_func(23)
      #入参类型是str使用注册入参类型为str的调度函数
      my_func('23')
    
    运行结果:
    generic func runs and input data type is <class 'float'>
    input data type is <class 'int'>
    input data type is <class 'str'>
    
  • wraps(func) 用于保留被装饰函数的元信息(如函数名,文档字符串,参数列表等等). 一般情况下自定义的装饰器中都要加这个装饰器,如果不加会导致在装饰器函数中丢失元数据信息.比如下面这个例子
    from functools import wraps
    
    def my_decorator(func):
      @wraps(func)
      def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
      return wrapper
    
    @my_decorator
    def test(a, b):
      '''
      this is a test func
      '''
      return a + b
    
    
    if __name__ == '__main__':
      print(f"doc is {test.__doc__}")
      print(f"name is {test.__name__}")
    
    当自定义的装饰器中使用了wraps装饰器输出结果为:
    doc is 
     this is a test func
    
    name is test
    
    如果注释掉wraps这一行
    def my_decorator(func):
     #@wraps(func)
     def wrapper(*args, **kwargs):
       res = func(*args, **kwargs)
       return res
     return wrapper
    
    输出结果:
    doc is None
    name is wrapper
    
    可见函数名被改变, 文档字符串也丢失.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值