装饰器
一、装饰器是什么?
- 装饰器实际就是一个函数
- 两个特别之处:参数就是一个函数,返回值是一个参数
它常用于有切面需求的场景,比如:插入日志、性能测试、事物处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计。
概括来说,装饰器的作用就是为已经存在的对象添加额外的功能。
@符号是装饰器的语法糖。
- 装饰器的组成方式: 函数 + 实参高阶函数 + 返回值高阶函数 + 嵌套函数 + 语法糖 = 装饰器
有关高阶函数的理解:
(1)把一个函数名当作实参传给另外一个函数(“实参高阶函数”)
(2)返回值中包含函数名(“返回值高阶函数”)
嵌套函数的理解:
(1)嵌套函数指的是在函数内部定义一个函数,而不是调用
语法糖:
写法:@xx,一般写在函数的上方
二、装饰器的使用
装饰器的使用方法很固定:
- 先定义一个装饰器
- 再定义业务函数或者类
- 最后把装饰器放在业务函数上
如:
def decorator(func): def wrapper(*args, **kw): return func() return wrapper @decorator def function(): print("hello, decorator")
实际上,装饰器并不是编码必须性,意思就是说,你不使用装饰器完全可以,它的出现,应该是使我们的代码结构更加清晰,代码更加优雅,将实现特定功能代码封装成装饰器,提高代码复用率,增强代码可读性。
多种装饰器:
- 装饰无参函数
- 装饰有参函数
- 带参数的装饰器
1、入门 -- 日志打印器
实现的功能:
- 在函数执行前,先打印一行日志,说明要执行函数了
- 在函数执行完,说明函数执行执行完成
def logger(func): def wrapper(*args, **kw): print("开始执行 {} 函数了:".format(func.__name__)) # 真是执行的是这行 func(*args, **kw) print("执行完成了") return wrapper
2、入门 -- 时间计算器
def logger(func): def wrapper(*args, **kw): t1 = time.time() # 真是执行的是这行 func(*args, **kw) t2 = time.time() # 计算一下时长 cost_time = t2 - t1 print("花费时间:{}秒:".format(cost_time)) return wrapper
3、进阶:带参数的函数装饰器
装饰器本身是一个函数,作为一个函数,如果不能传递参数,那这个函数的功能就会很受限,只能执行固定的逻辑。这意味着,如果装饰器的逻辑代码的执行需要根据不同的场景进行调整,若不能传参,我们就要写两个装饰器,这显然是不合理的。
怎么实现装饰器,让其可以实现传参?会比较复杂,需要两层嵌套
def say_hello(country): def wrapper(func): def deco(*args, **kw): if country == 'china': print("您好") elif country == "america": print('hello') else: return None # 真正执行函数的地方 func(*args, **kw) return deco return wrapper
4、高阶:不带参数的类装饰器
以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时长发现还有基于类实现的装饰器。基于类装饰器的实现,必须实现 __call__和__init__两个内置函数。
__init__:接收被装饰器函数
__call__:实现装饰逻辑
以日志打印为简单例子为例:
class logger(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwds): print("[INFO]: the function {func}() is runcing ...".format(func=self.func.__name__)) return self.func(*args, **kwds) @logger def say(something): print("say {}!".format(something))
5、高阶:带参数的类装饰器
指定日志的级别,这种情况就需要给类装饰器传入参数,给这个函数指定级别了。
带参数和不带参数的类装饰器有很大的不同。
__init__:不再接收被装饰函数,而是接受传入参数
__call__:接收被装饰函数,实现装饰逻辑
class logger(object): def __init__(self, level='INFO'): self.level = level def __call__(self,func): def wrapper(*args, **kwds): print("[{level}}]: the function {func}() is runcing ...".format(level=self.level ,func=self.func.__name__)) func(*args, **kwds) return wrapper @logger(level='WARNING') def say(something): print("say {}!".format(something))
6、使用偏函数与类实现装饰器
绝大多数装饰器都是基于函数和闭包实现的,但这并非是制造装饰器的唯一方式。
实际上,python对某个对象是否能通过装饰器(@decorator)形式使用只有一个要求:decorator必须是一个“可被调用(callable)的对象“。
除了函数之外,类也可以是callable对象,只要实现了__call__函数。还有容易被人忽略的偏函数也是callable对象。
接下来分析,DelayFunc是一个实现了__call__的类,delay返回一个偏函数,在这里delay就可以作为一个装饰器。
import time import functools class DelayFunc(): def __init__(self, duration, func): self.duration = duration self.func = func def __call__(self, *args, **kwds): print(f'Wait for {self.duration} seconds...') time.sleep(self.duration) return self.func(*args, **kwds) def eager_call(self, *args, **kwds): print("Call without delay") return self.func(*args, **kwds) def delay(duration): """ 装饰器:推迟某个函数的执行 同时提供 eager_call 方法立即执行 """ # 此处为了避免定义额外函数 # 直接使用 functools.partial 帮助构造DelayFunc 实例 return functools.partial(DelayFunc, duration) # 业务逻辑 @delay(duration=2) def add(a, b): return a + b
7、装饰类的装饰器
装饰器用在类上,并不是很常见,但是只要熟悉装饰器的实现过程,就不难以实现对类的装饰。
instances = {} def singleton(cls): def get_instance(*args, **kw): cls_name = cls.__name__ print("======1======") if not cls_name in instances: print("======2======") instance = cls(*args, **kw) instances[cls_name] = instance return instances[cls_name] return get_instance class User: _instance = None def __init__(self, name): print('======3======') self.name = name
8、wrapper装饰器
其作用就是将被修饰的函数的一些属性值赋值给修饰器函数(wrapper),最终让属性的显示更符合我们的直觉。
9、内置装饰器:property
问题:直接将属性给暴露出来,这样写起来虽然很简单,但是并不能对属性的值做合法性限制。
用@property装饰过的函数,会将一个函数定义成一个属性,属性的值就是该函数return的内容。同时,会将这个函数变成另一个装饰器,就像我们后面使用的@age.setter和@age.deleter
class Student: def __init__(self, name): self.name =name @property def math(self): return self._math @math.setter def math(self, value): if 0 <= value <= 100: self._math = value else: raise ValueError("Valid value must be in [0,100] ")
10、常用内置装饰器
@staticmethod:静态方法
@classmethod:类方法
参考:
https://zhuanlan.zhihu.com/p/269012332
浙公网安备 33010602011771号