简介

  1. 装饰器:在不修改被装饰函数的代码情况下,增强其功能。这个类似于Java中的面向切片编程。它本质也是一个函数,接受一个被装饰函数作为参数,返回增强版的与被装饰函数同名的函数
  2. 闭包是装饰器的基础,python中的闭包详见python之函数与闭包

装饰器的实现

  1. 装饰器的语法如下:
    1. 使用语法糖:在被装饰的函数之前加上@装饰器函数名或者在被装饰的类之上加上@装饰器类名。这个常用
    @decorator
    def decorated_function():
        pass
    
    1. 显示的调用装饰器。如果在一个函数上需要使用多个装饰器,这种写法可读性差
    def decorated_function():
        pass
    decorated_function = decorator(decorated_function)
    
  2. 用函数实现装饰器。
    1. 简单的示例如下所示:
    # 需求,给login接口增加日志记录的功能
    from loguru import logger
    
    def my_decorator(function):
        '''
        function为被装饰函数
        '''
        def wrapped(*args, **kwargs):
            # 被装饰函数调用之前执行的操作
            logger.info(f'日志记录开始')
    
            # 执行被装饰函数
            ret = function(*args, **kwargs)
    
            # 被装饰函数调用之后执行的操作
            logger.info(f'日志记录结束')
            return ret
        return wrapped
    
    
    def login(user_name, password):
        if user_name == 'admin' and password == '123456':
            print('login success')
        else:
            print('login failed')
    
    # 显式的调用装饰器
    login = my_decorator(login)
    login('admin', '123456')
    
    1. 带参数的装饰器:在装饰器之上再套一个函数
    # 需求:现有一个登录接口,1. 需要增加日志功能登录接口 2. 可以支持微信和QQ登录
    
    from loguru import logger
    def my_decorator_plus(type):
        def my_decorator(function):
            def wrapper(*args, **kwargs):
                # 微信登录
                if type == 0:
                    logger.info(f'微信登录日志记录开始')
    
                    ret = function(*args, **kwargs)
    
                    logger.info(f'微信登录日志记录结束')
                # QQ登录
                elif type == 1:
                    logger.info(f'QQ登录日志记录开始')
    
                    ret = function(*args, **kwargs)
    
                    logger.info(f'QQ登录日志记录结束')
                return ret
            return wrapper
    
        return my_decorator
    
    
    @my_decorator_plus(0)
    def login(user_name, password):
        if user_name == 'admin' and password == '123456':
            print('login success')
        else:
            print('login failed')
    
    login('admin', '123456')
    
  3. 自定义类实现装饰器。
    1. __call__方法介绍:这个方法可以将类的实例化对象转化成可调用对象。可调用对象可以直接调用__call__方法或者以对象名称()的形式调用。示例如下:
    class Student:
        def __init__(self, name):
            super().__init__()
            self.name = name
        # 定义__call__方法后,Student类对象实例化的对象成为一种可调用对象
        def __call__(self):
            print('hi,' + f'{self.name}')
            
    stu = Student("zss")
    print(callable(stu)) # 检查一个对象是否可调用,True
    stu.__call__() # hi,zss
    stu() # hi,zss
    
    1. 实现的类装饰器如下所示
    from typing import Any
    from loguru import logger
    
    class MyDecorator:
        def __init__(self, function) -> None:
            self.function = function
        
        def __call__(self, *args: Any, **kwds: Any) -> Any:
            # 被装饰函数调用之前执行的操作
            logger.info('日志记录开始')
    
            # 执行被装饰函数
            ret = self.function(*args, **kwds)
    
            # 被装饰函数调用之后执行的操作
            logger.info(f'日志记录结束')
            return ret
        
    
    # 使用语法糖使用装饰器
    @MyDecorator
    def login(user_name, password):
        if user_name == 'admin' and password == '123456':
            print('login success')
        else:
            print('login failed')
    
    login('admin', '123456')
    

装饰器的应用场景

1.在方法定义的开头能够将其定义为类方法或者静态方法
  1. 不使用装饰器前,手动将函数转化为类方法或者静态方法
class Test:
    def clsFunc(cls):
        print("this is class Method")
    def staFunc():
        print("this is static Method")

    # 将函数转化为类方法
    clsMethod = classmethod(clsFunc)
    # 将函数转化为静态方法
    staMethod = staticmethod(staFunc)

Test.clsMethod()
Test.staMethod()
  1. 使用装饰器后,方便将函数转化为类方法或者静态方法。如下示例使用了@classmethod@staticmethod两个装饰器,被装饰函数分别为clsFunc和staFunc
class Test:
    @classmethod    # 等同于clsFunc = classmethod(clsFunc)
    def clsFunc(cls):
        print("this is class Method")
    @staticmethod   # 等同于staFunc = staticmethod(staFunc)
    def staFunc():
        print("this is static Method")

Test.clsFunc()
Test.staFunc()
2.上下文装饰器
  1. 上下文装饰器确保函数可以运行在正确的上下文中,或者在函数前后运行一些代码。
from threading import RLock
lock = RLock()

def synchronized(function):
    def _synchronized(*args, **kw):
        lock.acquire()
        try:
            return function(*args, **kw)
        finally:
            lock.release()
    return _synchronized

@synchronized
def thread_salf():
    pass

# 上述程序的缺点:如果函数的执行逻辑比较多,则这个方法加锁的粒度大
3.@property装饰器
  1. 装饰器:在代码运行期间动态增加函数或者方法。在类中的方法使用@property修饰,则会将该属性作为类属性,可以像属性一样的方式通过对象.方法名或者类名.方法名来访问方法。
  2. 示例如下:
class Student:
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return self.__name
stu = Student("zss")
print(stu.name) # zss