装饰器

作者: GHope | 来源:发表于2018-11-29 19:36 被阅读68次

    装饰器 - 用装饰函数去执行被装饰函数并添加额外的功能 - 代理模式
    程序中跟正常业务逻辑没有必然联系而且会重复使用的功能称为横切关注功能
    横切关注功能不应该写在业务逻辑代码上而应该使用装饰器或中间件来完成
    这种编程理念也被称为面向切面编程(AOP - Aspect Oriented Programming)

    简单装饰器

    以下是一个空壳装饰器的简单实现,其中ret_value上下可以添加装饰器实现的功能。此处需要理解的两个知识点是wraps对装饰器的装饰作用和(*args, *kwargs)起到的作用。首先说(*args, *kwargs)的作用,*args表示不定参数(元组),**kwaegs表示关键字参数(字典),通俗的可以理解为通过此设置表示被装饰的函数可以拥有任意参数。其次说wraps,它主要起到两个作用。第一个作用:把原始函数的__name__等属性复制到wrapper()函数,防止某些依赖函数签名的代码执行出错;第二个作用:可以调用__wrapped__取消装饰器(获得被装饰之前的函数)。

    from functools import wraps
    
    
    def record_time(func):
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            
            ret_value = func(*args, **kwargs)
            
            return ret_value
    
        return wrapper
    

    测试函数运行时间

    下面是对上述装饰器的简单应用。实现的功能是获取函数运行的时间:

    from functools import wraps
    from time import time
    from random import randint
    
    
    def record_time(func):
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time()
            ret_value = func(*args, **kwargs)
            print(func.__name__, time() - start)
            return ret_value
    
        return wrapper
    
    
    # 测试用例
    @record_time()
    def foo():
        print('hello, world!')
        sleep(randint(1, 5))
    
    
    def main():
        for _ in range(3):
            foo()
    
    if __name__ == '__main__':
        main()
    

    关于if __name__ == '__main__':的作用说明:由于每个python模块(python文件)都包含内置的变量__name__,当运行模块被执行的时候,__name__等于文件名(包含了后缀.py)。如果import到其他模块中,则__name__等于模块名称(不包含后缀.py)。而“__main__”等于当前执行文件的名称(包含了后缀.py)。所以当模块被直接执行时,__name__ == '__main__'结果为真;而当模块被import到其他模块中时,__name__ == '__main__'结果为假,就是不调用对应的方法。简而言之就是:__name__是当前模块名,当模块被直接运行时模块名为__main__ 。当模块被直接运行时,代码将被运行,当模块是被导入时,代码不被运行。

    经过上述测试发现该装饰器器与print产生耦合,导致我们得到函数运行时间只能直接通过控制台输出。如果我们想拿到它,将是一件十分麻烦的事,所以下面就想办法解除该耦合。

    装饰器函数解耦合

    通过在外层再定义函数,包含装饰器,以传参(自定义函数作为参数)的方式解除耦合。如果传的参数为print,则和之前的结果一样;如果是自定义的参数,则实现自己需要的功能。

    from functools import wraps
    from time import time, sleep
    from random import randint
    
    
    def record_time(output):
    
        def decorate(func):
    
            @wraps(func)
            def wrapper(*args, **kwargs):
                start = time()
                ret_value = func(*args, **kwargs)
                output(func.__name__, time() - start)
                return ret_value
    
            return wrapper
    
        return decorate
    
    
    def log_to_file(fn, duration):
        with open('result.log', 'a') as fs:
            fs.write('%s: %.3f秒\n' % (fn, duration))
    
    
    #@record_time(print)
    @record_time(log_to_file)
    def foo():
        print('hello, world!')
        sleep(randint(1, 5))
    
    
    def main():
        for _ in range(3):
            foo()
    
    
    if __name__ == '__main__':
        main()
    

    此时已经实现了关于print的耦合问题,下面讨论用类的定义实现装饰器。

    类实现装饰器

    通过类的初始化方法即可解装饰器操作引起的耦合,之后的实现方法和函数实现大体类似。

    class RecordTime(object):
    
        def __init__(self, output):
            self.output = output
    
        def __call__(self, func):
    
            @wraps(func)
            def wrapper(*args, **kwargs):
                start = time()
                ret_value = func(*args, **kwargs)
                self.output(func.__name__, time() - start)
                return ret_value
    
            return wrapper
    

    需要注意的此处使用了 __call__ 的魔术方法,该方法是类可以像函数一样的被调用。所以可以当做装饰器使用。

    装饰器实现单例模式

    from functools import wraps
    
    
    def singleton(cls):
        instances = {}
    
        @wraps(cls)
        def wrapper(*args, **kwargs):
            if cls not in instances:
                instances[cls] = cls(*args, **kwargs)
            return instances[cls]
    
        return wrapper
    
    
    @singleton
    class Singleton(object):
        pass
    

    相关文章

      网友评论

      本文标题:装饰器

      本文链接:https://www.haomeiwen.com/subject/rghbqqtx.html