美文网首页
python拾遗3 - 装饰器

python拾遗3 - 装饰器

作者: 天命_风流 | 来源:发表于2020-05-04 00:44 被阅读0次

    什么是装饰器

    所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。

    Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

    装饰器可以存在的四个原因

    • 函数是对象,可以把函数赋予变量:
    def func(message):
        print('Got a message: {}'.format(message))
        
    send_message = func
    send_message('hello world')
    
    # 输出
    Got a message: hello world
    
    • 函数可以当做参数:
    def get_message(message):
        return 'Got a message: ' + message
    
    
    def root_call(func, message):
        print(func(message))
        
    root_call(get_message, 'hello world')
    
    # 输出
    Got a message: hello world
    
    • 函数内可以嵌套函数:
    def func(message):
        def get_message(message):
            print('Got a message: {}'.format(message))
        return get_message(message)
    
    func('hello world')
    
    # 输出
    Got a message: hello world
    
    • 函数的返回值可以是函数:
    def func_closure():
        def get_message(message):
            print('Got a message: {}'.format(message))
        return get_message
    
    send_message = func_closure()
    send_message('hello world')
    
    # 输出
    Got a message: hello world
    

    装饰器的本质

    def my_decorator(func):
        def wrapper():
            print('wrapper of decorator')
            func()
        return wrapper
    
    def greet():
        print('hello world')
    
    greet = my_decorator(greet)
    greet()
    
    # 输出
    wrapper of decorator
    hello world
    

    其中,greet = my_decorator(greet) 可以改为 @my_decorator,这也就是装饰器的本质。

    可以接受任意参数的装饰器

    由于你不知道要用装饰器装饰什么函数,以及这些函数都有些怎样不同的参数,所以你需要使用 *args 和 **kwargs 接受任意参数:

    def my_decorator(func):
        def wrapper(*args, **kwargs):
            print('wrapper of decorator')
            func(*args, **kwargs)
        return wrapper
    

    为装饰器定义参数

    有时,我们要为装饰器指定参数:

    def repeat(num):
        def my_decorator(func):
            def wrapper(*args, **kwargs):
                for i in range(num):
                    print('wrapper of decorator')
                    func(*args, **kwargs)
            return wrapper
        return my_decorator
    
    
    @repeat(4)
    def greet(message):
        print(message)
    
    greet('hello world')
    
    # 输出:
    wrapper of decorator
    hello world
    wrapper of decorator
    hello world
    wrapper of decorator
    hello world
    wrapper of decorator
    hello world
    

    原函数变形的问题

    使用装饰器装饰过的函数会产生变化(接上面的代码):

    greet.__name__
    ## 输出
    'wrapper'
    
    help(greet)
    # 输出
    Help on function wrapper in module __main__:
    
    wrapper(*args, **kwargs)
    

    可以发现,函数被装饰以后,它的信息改变了。为了解决这个问题,我们通常使用内置的装饰器 @functools.warp 解决,它可以帮助保留原函数的元信息:

    import functools
    
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('wrapper of decorator')
            func(*args, **kwargs)
        return wrapper
        
    @my_decorator
    def greet(message):
        print(message)
    
    greet.__name__
    
    # 输出
    'greet'
    

    类装饰器

    我们可以使用类的 __call__( ) 方法实现装饰器的功能(但是这有一定的问题,所以不推荐使用这种方式):

    class Count:
        def __init__(self, func):
            self.func = func
            self.num_calls = 0
    
        def __call__(self, *args, **kwargs):
            self.num_calls += 1
            print('num of calls is: {}'.format(self.num_calls))
            return self.func(*args, **kwargs)
    
    @Count
    def example():
        print("hello world")
    
    example()
    
    # 输出
    num of calls is: 1
    hello world
    
    example()
    
    # 输出
    num of calls is: 2
    hello world
    ...
    
    

    装饰器的嵌套

    装饰器也可以嵌套使用,下面两块代码有相同的效果:

    @decorator1
    @decorator2
    @decorator3
    def func():
        ...
    
    decorator1(decorator2(decorator3(func)))
    

    你可以看下面的这个例子帮助理解:

    import functools
    
    def my_decorator1(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('execute decorator1')
            func(*args, **kwargs)
        return wrapper
    
    
    def my_decorator2(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('execute decorator2')
            func(*args, **kwargs)
        return wrapper
    
    
    @my_decorator1
    @my_decorator2
    def greet(message):
        print(message)
    
    
    greet('hello world')
    
    # 输出
    execute decorator1
    execute decorator2
    hello world
    

    装饰器用例

    • 身份认证:认证用户是否登陆
    import functools
    
    def authenticate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            request = args[0]
            if check_user_logged_in(request): # 如果用户处于登录状态
                return func(*args, **kwargs) # 执行函数post_comment() 
            else:
                raise Exception('Authentication failed')
        return wrapper
        
    @authenticate
    def post_comment(request, ...)
        ...
    
    • 日志记录:记录函数的调用时间,并输出到日志
    import time
    import functools
    
    def log_execution_time(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            res = func(*args, **kwargs)
            end = time.perf_counter()
            print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
            return res
        return wrapper
        
    @log_execution_time
    def calculate_similarity(items):
        ...
    
    • 输入合理性检查:检查输入的数据是否合法
    import functools
    
    def validation_check(input):
        @functools.wraps(func)
        def wrapper(*args, **kwargs): 
            ... # 检查输入是否合法
        
    @validation_check
    def neural_network_training(param1, param2, ...):
        ...
    
    • 缓存:pylru 模块为我们提供了缓存能力,我们可以用他缓存很多东西,例如:指定函数在指定参数下的计算结果
    @lru_cache
    def check(param1, param2, ...) # 检查用户设备类型,版本号等等,由于很多用户的设备和版本重复,所以我们可以使用缓存提升性能
        ...
    

    元类(metaclass)

    装饰器用于在函数创建前对函数进行装饰。同样,我们可以使用 元类(metaclass)在类创建前对类进行定制。
    但是,这种定制是非常危险的,因为它会带来代码逻辑的改变。所以,从这个角度上来说,我们不推荐使用元类这个牛x 的功能。
    当然,如果你觉得有必要,可以自己学习一下

    相关文章

      网友评论

          本文标题:python拾遗3 - 装饰器

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