Python装饰器

142 阅读4分钟

函数回顾

函数可以作为变量

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
greet = my_decorator(greet) # 返回装饰后的结果
greet()

# 输出
wrapper of decorator
hello world

另一种语法糖的写法就是注解

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator # 其他的函数也可以使用这个注解
def greet():
    print('hello world')

greet()

带参数的装饰器

def my_decorator(func):
    def wrapper(message):# 传递的参数
        print('wrapper of decorator')
        func(message)
    return wrapper


@my_decorator
def greet(message):
    print(message)


greet('hello world')

# 输出
wrapper of decorator
hello world

如果有函数也需要这个注解,但是函数有多个参数,怎么办

def my_decorator(func):
    def wrapper(*args, **kwargs): # 这样就是传递任意数量和任意类型的参数了
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

*args, **kwargs这样就是传递任意数量和任意类型的参数了

带有自定义参数的装饰器

因为注解也是一个函数,所以自己也可以有参数


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)

出输出告诉我们,元信息变了,不在时greet函数了 增加 @functools.wraps(func)注解,保留原函数的元信息


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'

类也可以作为装饰器

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

...

装饰器的嵌套


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, ...):
    ...

LRU cache,在 Python 中的表示形式是@lru_cache。@lru_cache会缓存进程中的函数参数和结果,当缓存满了以后,会删除 least recenly used 的数据。

缓存

@lru_cache
def check(param1, param2, ...) # 检查用户设备类型,版本号等等
    ...

总结

通过基础和实例,了解了装饰器 主要是在不修改原函数的基础上,修改了函数的行为,扩展性好

相关问答

  1. 我感觉python的装饰器的应用场景有点像AOP的应用场景,把一些常用的业务逻辑分离,提高程序可重用性,降低耦合度,提高开发效率
  2. 请教下,为什么count那儿是单例模式吗?为什么二次执行会加1?

作者回答:因为num_calls这个变量是类变量,不是具体的实例变量,二次执行相当于调用了函数__call__两次,因此变量num_calls会变为2

但是使用self赋值的变量应该就是实例变量 ??