装饰器

一、装饰器是什么?

  • 装饰器实际就是一个函数
  • 两个特别之处:参数就是一个函数,返回值是一个参数

  它常用于有切面需求的场景,比如:插入日志、性能测试、事物处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计。

  概括来说,装饰器的作用就是为已经存在的对象添加额外的功能。

  @符号是装饰器的语法糖。

 

  • 装饰器的组成方式: 函数 + 实参高阶函数 + 返回值高阶函数 + 嵌套函数 + 语法糖 = 装饰器

    有关高阶函数的理解

      (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

 

posted @ 2021-09-15 16:00  洪二  阅读(99)  评论(0)    收藏  举报