背景
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__
方法,类装饰器 同样可以实现不带参数的函数装饰器以及带参数的函数装饰器效果.- 不带参数的装饰器
运行结果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
- 带参数的装饰器
输出结果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)
可见被lru_cache装饰的函数在相同入参多次调用时只调用了一次,而普通的函数则是调用多次.catched function runs normal function runs normal function runs normal function runs normal function runs normal function runs
- 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) 用于保留被装饰函数的元信息(如函数名,文档字符串,参数列表等等). 一般情况下自定义的装饰器中都要加这个装饰器,如果不加会导致在装饰器函数中丢失元数据信息.比如下面这个例子
当自定义的装饰器中使用了wraps装饰器输出结果为: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
输出结果:def my_decorator(func): #@wraps(func) def wrapper(*args, **kwargs): res = func(*args, **kwargs) return res return wrapper
可见函数名被改变, 文档字符串也丢失.doc is None name is wrapper