python---装饰器
前言:引入(函数的知识):
python中,函数是对象,可以赋值给变量;
变量可以直接代替函数,接收参数,返回结果;
函数内可以定义函数;
函数可以作为参数被传递;
函数的返回值也可以是函数(闭包);
函数是有一些元信息的:
python的函数名的使用 和 函数变量的操作: https://www.cnblogs.com/zyxnhr/p/12284852.html
闭包的一个例子:
1 def func_closure(): # 这里外部函数,并没有定义要接收参数 2 def get_message(message): 3 print('Got a message: {}'.format(message)) 4 return get_message 5 6 send_message = func_closure() # 执行func_closure()这个函数对象,然后其返回内部的函数对象的get_message的引用,将其赋值给变量send_message。
7 所以,send_message拿到的是内部函数对象get_message的引用 8 send_message('hello world') # 该变量可以作为函数,接收参数
# 输出
Got a message: hello world
一、函数装饰器:
功能:装饰器函数,通过包装(wrap)来为原函数增加一些功能,而不需要 修改 原函数 本质:一个函数,接收一个对象,也返回一个对象 应用场景:有切面需求(aop)的场景,比如插入日志,权限校验(身份认证),事务处理,缓存,性能测试等。通过装饰器,可以抽象出大量公用的代码,提高效率,可读性。
1、简单的无参装饰器
def decorator_example(func): def wrapper(): # 装饰器内部函数不接受参数 print("This is a simple decorator!") func() return wrapper @decorator_example def func_example(): print("I am a test function and I will be wrapped") func_example() ''' 执行结果如下: This is a simple decorator! I am a test function and I will be wrapped ''' #### 上面的代码也可以这样写,便于理解func_example函数发生了什么##### def decorator_example(func): def wrapper(): print("This is a simple decorator!") func() return wrappe def func_example(): print("I am a test function and I will be wrapped" func_example = decorate_example(func_example) # func_example变量指向了新的对象,即执行decorate_example函数后,返回的wrapper对象 func_example() # 调用的其实是wrapper对象,原来的func_example对象在其内部被调用
2、带参数的装饰器
1 def decorator_example(func): 2 def wrapper(message): #装饰器内部函数接受一个参数 3 print("This is a simple decorator, named {}".format(message)) 4 func() 5 6 return wrapper 7 8 9 @decorator_example 10 def func_example(): 11 print("I am a test function and I will be wrapped") 12 13 func_example("钢铁侠") 14 15 ''' 16 结果如下: 17 This is a simple decorator, named 钢铁侠 18 I am a test function and I will be wrapped 19 '''
3、可以接受任意参数的装饰器(通常是这种情况)
Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数
1 def decorator_example(func): 2 def wrapper(*args, **kwargs): # 通过*args,**kwargs ,装饰器内部函数可以接受任意多个参数 3 print("This is a simple decorator, named {}, {}".format(args[0], args[1])) 4 func() 5 6 return wrapper 7 8 @decorator_example 9 def func_example(): 10 print("I am a test function and I will be wrapped") 11 12 13 func_example("钢铁侠", "大家好!")
'''
结果如下:
This is a simple decorator, named 钢铁侠, 大家好!
I am a test function and I will be wrapped
'''
4、装饰器函数自定义参数(注意被包装函数上面注解的变化)
1 def loop_print(times): # 注意,这里的times被传到了最内层的wrapper对象 2 def decorator_example(func): 3 def wrapper(*args, **kwargs): 4 for i in range(times): 5 print("This is a simple decorator, named {}, {}".format(args[0], args[1])) 6 func() 7 return wrapper 8 return decorator_example 9 10 11 @loop_print(3) # 这里返回的是decorator_example对象的引用,其中'3'这个参数已经存储在对象内部 12 def func_example(): 13 print("I am a test function and I will be wrapped") 14 15 16 func_example("钢铁侠", "大家好!") 17 18 ''' 19 下面是结果: wrapper内部的语句被循环执行了 3 次 20 This is a simple decorator, named 钢铁侠, 大家好! 21 I am a test function and I will be wrapped 22 This is a simple decorator, named 钢铁侠, 大家好! 23 I am a test function and I will be wrapped 24 This is a simple decorator, named 钢铁侠, 大家好! 25 I am a test function and I will be wrapped 26 27 '''
5、注意:上面的4种情形中,func_example()函数被装饰后,元信息改变了,变成wrapper()函数了,下面的代码进行了验证
print(func_example.__name__) 结果:wrapper
那么,如何阻止这种改变呢?
1 import functools 2 def decorator_example(func): 3 @functools.wraps(func) #帮助原函数func保留元信息,将原函数的元信息拷贝到了对应的装饰器函数里 4 def wrapper(message): 5 print("This is a simple decorator, named {}".format(message)) 6 func() 7 8 return wrapper 9 10 @decorator_example 11 def func_example(): 12 print("I am a test function and I will be wrapped") 13 14 func_example("钢铁侠") 15 16 ### 重点来了### 17 # 下面代码的执行结果是:func_example 18 print(func_example.__name__)
6、多个装饰器:
@decorator3 @decorator2 @decorator1 def func_example(): pass
形式上等效于 decorator3(decorateor2(decorateor1(func_example))) 执行 顺序;decorateor3 -> decoratror2 -> decorateor1 -> func_example
import functools
def decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("我是decorator1")
func()
return wrapper
def decorator2(func):
functools.wraps(func)
def wrapper(*args, **kwargs):
print("我是decorator2")
func()
return wrapper
@decorator2
@decorator1
def func_example():
print("我是func_example")
func_example()
'''
执行结果:
我是decorator2
我是decorator1
我是func_example
'''
二、类装饰器:
主要依靠调用函数__call__(),类的同一个对象实例,每被调用一次,对象的__call__()函数就会被调用一次 从__init__()构造函数,传入原函数;原函数在__call__()中完成逻辑处理,并返回新的函数
1 class decorate_class(): 2 def __init__(self, func): 3 self.func = func 4 self.num = 0 5 6 def __call__(self, *args, **kwargs): 7 self.num = self.num +1 8 print('this object is called {} times.'.format(self.num)) 9 self.func(*args, **kwargs) 10 11 @decorate_class # 被类装饰器 装饰 12 def func_example(): 13 print("I am a test function and I will be wrapped") 14 15 func_example()
三、实际应用:
1、日志记录:
1 ''' 2 功能:线上测试某个函数的执行时间,看是否耗时过长,导致系统延迟(latency)增加,常用装饰器 3 ''' 4 import functools 5 import time 6 7 def log_execute_time(func): 8 @functools.wraps(func) 9 def wrapper(*args, **kwargs): 10 start_time = time.perf_counter() 11 res = func() 12 end_time = time.perf_counter() 13 print("{} took {} ms.".format(func.__name__, (end_time - start_time) * 1000)) 14 return res 15 return wrapper 16 17 @log_execute_time 18 def func_example(): 19 sum_ = 0 20 for i in range(10000): 21 sum_ += i 22 return sum_ 23 24 print("函数执行结果是: ", func_example()) 25 26 ''' 27 执行结果: 28 func_example took 0.4104 ms. 29 函数执行结果是: 49995000 30 '''
2、输入的合理性检查
1 '''' 2 机器学习场景,在调用集群进行训练前,利用装饰器对输入(往往是大的json文件)和参数进行合理性检查,防止在训练中途出现错误,耽误时间 3 ''' 4 import functools 5 6 def input_validation_check(func): 7 @functools.wraps(func) 8 def wrapper(*args, **kwargs): 9 ... # 校验输入的数据和参数是否合法 10 11 @input_validation_check 12 def neural_network_training(param1, param2, ...): 13 ...
3、缓存
python中的内置的LRU(least recently used) cache , 会缓存进程中的参数和结果,变现形式为@lru_cache
functools.lru_cache(maxsize=None, typed=False)有两个可选参数
(1)maxsize为最多可以缓存的调用结果数量;若为None则不限制数量;若为2的次幂性能最佳
(2)typed若为True,则会把不同的参数类型得到的结果分开保存
参考 :https://blog.konghy.cn/2016/04/20/python-cache/
1 import time 2 3 def fib(n): 4 return 1 if n in [1, 2] else fib(n - 1) + fib(n - 2) 5 start = time.perf_counter() 6 res = fib(35) 7 end = time.perf_counter() 8 print("n=35时,结果是{}, 花费了{}ms ".format(res, (end - start) *1000)) 9 ''' 10 执行就结果: 11 n=35时,结果是9227465, 花费了2763.4111999999996ms 12 ''' 13 14 #############分割线,以下是使用lru_cache之后######################### 15 16 from fundtools import lru_cache 17 import time 18 19 @lru_cache(10) # 这里lru_cache 20 def fib(n): 21 return 1 if n in [1, 2] else fib(n - 1) + fib(n - 2) 22 start = time.perf_counter() 23 res = fib(35) 24 end = time.perf_counter() 25 print("n=35时,结果是{}, 花费了{}ms ".format(fib(35), (end - start) + 1000)) 26 27 ''' 28 执行就结果: 29 n=35时,结果是9227465, 花费了0.025799999999999997ms # 简直快到不行 30 '''
另外,服务端的代码,经验有许多对移动端设备的检查(比如设备型号,版本号等),通常使用缓存装饰器 来 包裹这些检查函数,避免其返回被调用,以此来增加效率