wl413911

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 '''

另外,服务端的代码,经验有许多对移动端设备的检查(比如设备型号,版本号等),通常使用缓存装饰器 来 包裹这些检查函数,避免其返回被调用,以此来增加效率

posted on 2020-05-31 19:22  wl413911  阅读(133)  评论(0编辑  收藏  举报