美文网首页
装饰器(decorator)

装饰器(decorator)

作者: 马尔代夫Maldives | 来源:发表于2021-09-11 16:05 被阅读0次

装饰器

https://www.zhihu.com/question/26930016

一、问题提出

有如下二个函数,(为什么这里函数要取名为wrapped后面讲):

def wrapped1():
    print('Run wrapped1!')

def wrapped2():
    print('Run wrapped2')

现在有一个新要求:执行这两个函数时都打印信息“Program start!”。
如何解决?
直接能想到的办法是分别在两个函数中加入print('Program start!'),像下面这样:

def wrapped1():
    print('Program start!')
    print('Run wrapped1!')

def wrapped2():
    print('Program start!')
    print('Run wrapped2')

但是这存在两个问题:
1)如果有10000个这样的函数,岂不是每个都要分别加代码,工作量极其巨大。
2)在函数中新增代码,势必会破坏原函数的完整性。

针对上述问题,有人提出,可以重新写一个函数new_func(),然后将这些不能动的函数作为new_func()的参数,在new_func()中先执行print('Program start!'),再执行对应的函数,像下面这样:

def new_func(func):
    print("Program start!")
    func()

new_func(wrapped1)
new_func(wrapped2)

这么做好像可行,但出现了另一个问题:
3)破坏了程序结构,原来用wrapped1()的地方,现在得改成new_func(wrapped1)

再考虑另一种实现方式,前面我们学过闭包,也可以实现上述过程,如下例。下述代码中wrapper是一个闭包函数。下述代码虽然也实现了要求,但同样存在问题3),无法直接通过wrapped1()执行函数。(注意 wrapper 的 *args 与**kwargs 参数, 保证了能完全传递所有参数,之后再将其传到 func函数中。)

def decorator(func):
    def wrapper(*args, **kwargs):
        print ('Program start!')
        return func(*args, **kwargs)
    return wrapper

def wrapped1():
    print('Run wrapped1!')

def wrapped2():
    print('Run wrapped2')
#####################################################
my_wrapped1 = decorator(wrapped1)
my_wrapped1()

二、函数装饰器

前面这段代码本质上已经是所谓的“装饰器”的效果了,只是在表现形式上还不够简洁,python为了让代码更简洁,创造了神奇的“@”语法糖,直接上代码:

def decorator(func):
    def wrapper(*args, **kwargs):
        print ('Program start!')
        return func(*args, **kwargs)
    return wrapper

@decorator
def wrapped1():
    print('Run wrapped1!')
    
@decorator
def wrapped2():
    print('Run wrapped2')
    
#####################################################
wrapped1()
wrapped2()
#输出
Program start!
Run wrapped1!
Program start!
Run wrapped2

上述代码展示了一个简单但完整的装饰器功能。比较本文最前的代码,此时我们执行wrapped1()和wrapped2(),都会先输出“Program start!”,但我们并没有对wrapped1和wrapped2函数做任何修改,只是在其定义前加了“@decorator”

上述代码中decorator()和wrapper()形成了闭包,其中wrapper()是闭包函数。decorator()的输入参数func是函数名,这个函数是要在闭包函数wrapper()中执行的。@decorator的作用就是去装饰其他函数(说白了就是把这句代码写在任意其他函数的定义之前),他将被装饰的函数传入decorator()

整个过程:
1)decorator()函数是装饰器,他把要装饰被装饰的两方联在一起;
2)wrapper()正是那个要装饰,即把wrapper中的某些东西装饰给别人(例如这里的print ('Program start!'));
3)wrapped1和wrapped2就是被装饰,你看他们顶上多了个帽子“@decorator”;
4)结果是执行wrapped1()和wrapped2()时,平白无故多出了'Program start!'。

前述代码中,函数wrapped1和wrapped2都是没有参数的函数,如果有参数怎么办?
答案是:完全不影响!但不要忘记参数*args和**kwargs,否则出错。

def decorator(func):
    def wrapper(*args, **kwargs):
        print ('Program start!')
        return func(*args, **kwargs)
    return wrapper

@decorator
def wrapped1(a,b):
    print(a+b)

###########################################
wrapped1(5,3)
#输出
Program start!
8

三、装饰器的作用

(1)避免重复写公共性代码,提高代码复用性,如这里的“Program start!”;
(2)在不改动原函数的前提下,为原函数增加了额外功能,如这里原来执行wrapped1(),只输出Run wrapped1!,现在还输出Program start!;
(3)在给原函数增加额外功能的同时,不破坏源程序的结构,如这里原来执行wrapped1()的地方,现在还是wrapped1()。

简单点就是,装饰器给已有的模块(函数、类)加上一些其他功能(这些功能可能好多模块都会用到),但又不需要在已有模块内部增加额外代码

装饰器最大的优势在于解决重复性操作,即将重复性代码写在装饰器中,谁想用就通过装饰器直接用即可,你只需要在每个函数上方加一个"@装饰器"即可。比如下面这个计算每个函数运行时间的装饰器:

import time

def run_time(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)  # 函数在这里运行
        end = time.time()
        cost_time = end - start
        print("func three run time {}".format(cost_time))
    return wrapper

@run_time
def fun_one():
    time.sleep(1)

@run_time
def fun_two():
    time.sleep(2)

@run_time
def fun_three():
    time.sleep(3)

####################
fun_one()
fun_two()
fun_three()
#输出
func three run time 1.0002892017364502
func three run time 2.000812292098999
func three run time 3.0008528232574463

装饰器的本质是闭包

四、装饰器的实际执行过程

def decorator(func):
    def wrapper(*args, **kwargs):
        print ('Program start!')
        return func(*args, **kwargs)
    return wrapper

@decorator
def wrapped1(a,b):
    print(a+b)

###########################################
wrapped1(5,3)
#输出
Program start!
8

上例中,最后我们执行了wrapped1(5,3),这里的wrapped虽然和定义时def wrapped1(a,b)的函数名相同,但实际上他们只是同名,已经不是定义时的那个wrapped1了。
因为本质上,装饰器是将原函数wrapped1作为装饰器函数decorator的一个参数,然后返回闭包函数wrapper,这个闭包函数会用到传进来的wrapped1函数,将wrapped1函数放在闭包函数内执行。
闭包的执行过程相当于:wrapped1=decorator(wrapped1),decorator(wrapped1)的返回值是闭包函数wrapper,相当于wrapped1=wrapper,可见,函数被装饰后,虽然与原函数同名,但实际上是装饰器内部的闭包函数。

五、带参数的装饰器

前面的装饰器,装饰的内容是“Program start!”,是固定不变的, 如果我们想动态指定装饰内容,那该怎么办?总不能老是去改装饰器函数吧。
我们可以这么做,如下例,在装饰器外部再套上一层函数info,用该函数的参数接收我们想要装饰的内容value,并将先前的 decorator 函数作为info的返回值。实际上是再生成了一个闭包结构,decorator与info组成闭包结构,wrapper与decorator组成闭包结构。
如此一来,只需要修改@info('Program 1 start!')中的内容就可以了。

def info(value):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print (value)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@info('Program 1 start!') #只要修改这里的值即可
def wrapped1(a,b):
    print(a+b)

@info('Program 2 start!') #只要修改这里的值即可
def wrapped2(a,b):
    print(a-b)
###########################################
wrapped1(5,3)
wrapped2(5,3)
#输出
Program 1 start!
8
Program 2 start!
2

六、wraps装饰器

一个函数不止有执行语句,还有__name__(函数名)等其他属性,例如:

def wrapped1(a,b):
    print(a+b)
    
print(wrapped1.__name__)
#输出
wrapped1

但前面的装饰器会将原函数的这些信息改变,比如下例中,本来输出的是“wrapped1”,经过装饰器后变成了“wrapper”,即变成了装饰函数的名称了:

def decorator(func):
    def wrapper(*args, **kwargs):
        print (value)
        return func(*args, **kwargs)
    return wrapper

@decorator
def wrapped1(a,b):
    print(a+b)

print(wrapped1.__name__)
#输出
wrapper

解决这一问题的办法是通过 functools 模块下的 wraps 装饰器:

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print (value)
        return func(*args, **kwargs)
    return wrapper

@decorator
def wrapped1(a,b):
    print(a+b)

print(wrapped1.__name__)
#输出
wrapped1

七、多层装饰器及执行顺序

先看下例:

def decorator(func):
    print('Run decorator!')
    def wrapper(*args, **kwargs):
        print('有火眼金睛了')
        return func(*args, **kwargs)
    return wrapper

print('cgx_test 1')

@decorator
def monkey_sun():
  print('吃桃子')
#####################################

print('cgx_test 2')
monkey_sun()

#输出
cgx_test 1
Run decorator!
cgx_test 2
有火眼金睛了
吃桃子

该例的装饰器与前面并没不同,只是在装饰器函数decorator内部增加了一句 print('Run decorator!');
程序中print('cgx_test 1')和print('cgx_test 2')是用于测试的标志。
从输出结果可以看出,装饰器函数decorator在执行monkey_sun()之前就被执行了(因为输出了Run decorator!),或者说当程序执行到装饰器语法糖@时,装饰器函数decorator就被执行了
@decorator
def monkey_sun():
即当程序执行完上两行代码时,实际上完成了decorator(func),我们知道装饰函数decorator的返回值是其内部闭包函数wrapper,也就意味着上述代代码执行完后,生成了一个wrapper函数,只是wrapper函数还没有被执行,wrapper的执行需要等到monkey_sun()这句话被执行,前面我们也分析过,最后的monkey_sun()本质就是wrapper函数,而非原来定义的monkey_sun函数。
了解了上述执行过程后,对于多重装饰就好理解了,见下例:

def decorator1(func):
    print('Run decorator1!')
    def wrapper1(*args, **kwargs):
        print('有火眼金睛了')
        return func(*args, **kwargs)
    return wrapper1

def decorator2(func):
    print('Run decorator2!')
    def wrapper2(*args, **kwargs):
        print('有金箍棒了')
        return func(*args, **kwargs)
    return wrapper2

def decorator3(func):
    print('Run decorator3!')
    def wrapper3(*args, **kwargs):
        print('有72变了')
        return func(*args, **kwargs)
    return wrapper3

print('cgx_test 1')

@decorator1
@decorator2
@decorator3
def monkey_sun():
  print('吃桃子')
#####################################

print('cgx_test 2')
monkey_sun()

#输出
cgx_test 1
Run decorator3!
Run decorator2!
Run decorator1!
cgx_test 2
有火眼金睛了
有金箍棒了
有72变了
吃桃子

该例中有三个装饰器decorator1-3,且在每个decorator函数内增加了一句“print('Run decorator*!')”,三个装饰器按顺序装饰了monkey_sun函数。

根据前面分析,装饰器函数decorator1-3会在语法糖@位置就被执行,从输出结果看确实是这样,因为在cgx_test 1和cgx_test 2之间输出了Run decorator3!、Run decorator2!、Run decorator1!。
根据输出结果的顺序可以判断,越是靠近被装饰函数的装饰器函数,越早执行,注意这句话的意思是装饰器函数decorator被执行,而非其内部的wrapper闭包函数,闭包函数只是decorator被执行后的返回结果,但还没执行它,这点特别要注意,从输出结果也可以看出,wrapper内的信息并没有在cgx_test 1和cgx_test 2之间输出。

接着我们执行了monkey_sun(),输出结果首先是“有火眼金睛”,可见是decorator1中的wrapper1最先执行,为什么反而是离被装饰函数最远的装饰器函数中的闭包最先执行呢?其实很好理解。
前面我们说了,离被装饰函数最近的装饰器函数最先被执行,即:
首先执行decorator3,它以monkey_sun函数作为输入(即decorator3(monkey_sun)),并返回了wrapper3;
然后执行decorator2,注意,此时它以上一步返回的wrapper3作为输入(即decorator2(wrapper3),并返回了wrapper2;
再执行decorator1,注意,此时它以上一步返回的wrapper2作为输入(即decorator1(wrapper2),并返回了wrapper1;
也就是说,最终我们得到的是wrapper1闭包函数;
根据前面分析,最后执行的monkey_sun(),本质上就是执行wrapper1(),因此首先输出了“有火眼金睛了”,而wrapper1中return func(*args, **kwargs)中的func是wrapper2,即着这句话就是执行wrapper2(),自然就输出了“有金箍棒了”,而wrapper2中return func(*args, **kwargs)中的func是wrapper3,即着这句话就是执行wrapper3(),自然就输出了“有72变”,而wrapper3中return func(*args, **kwargs)中的func才是monkey_sun,即着这句话就是执行monkey_sun(),自然就输出了“吃桃子”.
至此,整个装饰器的工作才算结束!
上述过程如下图所示:

无标题.png

八、类中的内置装饰器

有三种我们经常会用到的装饰器, @classmethod、 @staticmethod、 @property,他们有个共同点,都是作用于类方法之上
详见:https://www.jianshu.com/writer#/notebooks/29416844/notes/92665962/preview

九、类装饰器

类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
详见:https://www.jianshu.com/writer#/notebooks/29416844/notes/92665962/preview

相关文章

网友评论

      本文标题:装饰器(decorator)

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