Python基础之装饰器

一、什么是装饰器?

装饰器本质上就是一个Python函数,它可以让其它函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。

装饰器的应用场景:比如插入日志,性能测试,事物处理,缓存等等场景。

二、装饰器的形成过程

现在我有一个需求,我想让你在不改变函数代码的情况下,测试出这个函数的执行时间:

import time


def func1():
    print("in func1")


def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner


func1 = timer(func1)
func1()

但是如果有多个函数,我都想让你测试他们的执行时间,你岂不是每个函数都要写一遍func1 = timer(func1)?

这样写非常麻烦,因为这些函数的函数名都是不相同的,有func1、func2,func3等等。

针对这种情况,python给我们提供了一个简单快捷的方法,那就是语法糖。

 1 import time
 2 
 3 
 4 def timer(func):
 5     def inner():
 6         start = time.time()
 7         func()
 8         print(time.time() - start)
 9     return inner
10 
11 
12 @timer  # ==> func1 = timer(func1)
13 def func1():
14     print("in func1")
15     
16     
17 func1()
装饰器 ——语法糖

刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数要怎么办呢?

 1 import time
 2 
 3 def timer(func):
 4     def inner(a):
 5         start = time.time()
 6         func(a)
 7         print(time.time() - start)
 8     return inner
 9 
10 
11 @timer
12 def func1(a):
13     print(a)
14 
15 func1(1)
装饰器——带参数的装饰器
 1 import time
 2 
 3 
 4 def timer(func):
 5     def inner(*args, **kwargs):
 6         start = time.time()
 7         result = func(*args, **kwargs)
 8         print(time.time() - start)
 9         return result
10     return inner
11 
12 
13 @timer #==> func1 = timer(func1)
14 def func1(a, b):
15     print("in func1")
16     
17 
18 @timer #==> func2 = timer(func2)
19 def func2(a):
20     print("in func2 and get a:%s" % a)
21     return "func2 end"
22 
23 
24 func1("aaa", "bbb")
25 print(func2("aa"))
装饰器——hold住所有参数的装饰器

上面的装饰器已经非常完美了,但是我们在正常情况下查看函数信息的方法却在此处全部失效了:

def index():
    """
    这是一个主页信息
    :return:
    """
    print("from index")


print(index.__doc__)    # 查看函数注释的方法
print(index.__name__)   # 查看函数名的方法

那么如何解决这个问题呢?

from functools import wraps


def deco(func):
    @wraps(func)    # 放在最内层函数最上方
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@deco
def index():
    """
    显示首页信息
    :return:
    """
    print("from index")


print(index.__doc__)
print(index.__name__)

三、开放封闭原则

装饰器是完美的遵循了这个开放封闭原则的。

那么什么是开放封闭原则呢?

我们可以从下面两个方面来看。

1. 对扩展是开放的

为什么要对扩展开放呢?

我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能,并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。

2. 对修改是封闭的

为什么要对修改封闭呢?

就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。

四、装饰器的主要功能和固定结构

def timer(func):
    def inner(*args, **kwargs):
        """执行函数之前要做的"""
        result = func(*args, **kwargs)
        """执行函数之后要做的"""
        return result
    return inner
装饰器的固定格式——普通版
1 from functools import wraps
2 
3 def deco(func):
4     @wraps(func)    # 加在最内层函数最上方
5     def wrapper(*args, **kwargs):
6         return func(*args, **kwargs)
7     return wrapper
装饰器的固定格式——wrapper版

五、带参数的装饰器

假如你有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉,你要怎么做?

如果一个一个的取消,那任务量也太大了吧。

万一,没过几天,你又需要用这些装饰器,岂不是要吐血。

那么解决办法,就是在装饰器上加上参数:

 1 def wrapper_out(flag):
 2     def wrapper(func):
 3         @wraps(func)
 4         def inner(*args, **kwargs):
 5             if flag:
 6                 print("执行函数之前要做的")
 7 
 8             result = func(*args, **kwargs)
 9 
10             if flag:
11                 print("执行函数之后要做的")
12 
13             return result
14         return inner
15     return wrapper
16 
17 
18 @wrapper_out(False)     # 通过传递True和False来控制装饰器内部的运行效果
19 def func():
20     pass
21 
22 
23 func()
带参数的装饰器

六、多个装饰器装饰一个函数

先执行下面这样一个代码

 1 def wrapper1(func):
 2     def inner(*args, **kwargs):
 3         print("111")
 4         result = func(*args, **kwargs)
 5         print("222")
 6         return result
 7     return inner
 8 
 9 
10 def wrapper2(func):
11     def inner(*args, **kwargs):
12         print("333")
13         result = func(*args, **kwargs)
14         print("444")
15         return result
16     return inner
17 
18 
19 @wrapper2
20 @wrapper1
21 def func():
22     print("this is func")
23 
24 func()
多个装饰器装饰一个函数

当执行完毕后,可以看到执行结果为:

333
111
this is func
222
444

执行顺序:首先@warpper1装饰器来,然后获取到一个新函数是wrapper1中的inner,然后执行@wrapper2。这个时候,wrapper2装饰的就是wrapper1中的inner了。

所以,执行顺序就像:第二层装饰器前(第一层装饰器前(目标)第一层装饰器后)第二层装饰器后。程序从左到右执行起来,这就是我们看到的结果。

 

posted @ 2018-11-26 21:17  _杨魏  阅读(289)  评论(0编辑  收藏  举报