五、装饰器

装饰器

装饰器简介

’装饰’代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。

概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。

装饰器经常用于有切面需求的场景,比如:

插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,

有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

语法

def 函数1(a):
    函数体1
    def 函数2():
        函数体2
        a()
    return 函数2
@函数名1
def 函数3():
    函数体3
'''
@函数1
def 函数3():
    函数体3
相当于:函数3 = 函数1(函数3)

详细原理如下:
    @函数1: 使用 函数1 装饰 函数3
装饰过程:
    1. 调用 函数1 从而执行 函数体1 和 return,即函数1 内所有的可执行代码
    2. 将 函数3 作为 函数名1 的参数,从而使得 函数2 内的 a() 变为 函数3(),从而 函数2 的内容发生改变:
        def 函数2():
            函数体2
            函数3()    ==> 执行函数体3
    3. 将 函数3 = 函数2 ,从而使 函数3 的内容发生改变:
        def 函数3():
            函数体2
            函数3()    ==> 此处代表原函数3,即执行函数体3
装饰完成后,被装饰的函数3不再是原来的内容,相当于一个毛配房被装饰了一个功能齐全的精装房,所代表的功能也因为不同的装饰发生改变
'''

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。

无参装饰器

如果想为下述函数添加统计其执行时间的功能

import time

def index():
    time.sleep(3)
    print('Welcome to the index page’)
    return 200

index() #函数执行

遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样

start_time=time.time()
index() #函数执行
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,

函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入

def wrapper(func): # 通过参数接收外部的值
    start_time=time.time()
    res=func()
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    return res

但之后函数的调用方式都需要统一改成

wrapper(index)
wrapper(其他函数)

这便违反了不能修改被装饰对象调用方式的原则,

于是我们换一种为函数体传值的方式,即将值包给函数,如下

def timer(func):
    def wrapper(): # 引用外部作用域的变量func
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

这样我们便可以在不修改被装饰函数源代码和调用方式的前提下

为其加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,

返回一个闭包函数wrapper重新赋值给变量名 /函数名index,如下

def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

home=timer(home)
home('egon')
#抛出异常
TypeError: wrapper() takes 0 positional arguments but 1 was given

之所以会抛出异常,是因为home(‘egon’)调用的其实是wrapper(‘egon’),

而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,

为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合,于是修正装饰器timer如下

def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

此时我们就可以用timer来装饰带参数或不带参数的函数了,

但是为了简洁而优雅地使用装饰器,

Python提供了专门的装饰器语法来取代index=timer(index)的形式,

需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,

且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

@timer # index=timer(index)
def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200
@timer # index=timer(home)
          def home(name):
    time.sleep(5)
    print('Welcome to the home page’,name)

如果我们有多个装饰器,可以叠加多个

@deco3
@deco2
@deco1
def index():
    pass

叠加多个装饰器也无特殊之处,上述代码语义如下:

index=deco3(deco2(deco1(index)))

有参装饰器

了解无参装饰器的实现原理后,我们可以再实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下

def deco(func):
    def wrapper(*args,**kwargs):
        编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
    return wrapper

如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下

def deco(func):
        def wrapper(*args,**kwargs):
            if driver == 'file':
                编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
            elif driver == 'mysql':
                编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
        return wrapper

函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,

不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,

用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到

def auth(driver):
    def deco(func):
        ……
    return deco

此时我们就实现了一个有参装饰器,使用方式如下

先调用auth_type(driver='file'),得到@deco,deco是一个闭包函数,
包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样

@auth(driver='file') 
def index():     
    pass
@auth(driver='mysql') 
def home():
    pass  

可以使用help(函数名)来查看函数的文档注释,

本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释

@timer
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page',name)

print(help(home))
'''
打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None

在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,

想要保留原函数的文档和函数名属性,需要修正装饰器

def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    wrapper.__doc__=func.__doc__
    wrapper.__name__=func.__name__
    return wrapper

按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下

from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

装饰器的作用

#指定一个装饰器,实现不同时间的限制
def zzw(a):
    pass
    def zhang(x,y,*ages,**kwargs):
        time_s = kwargs.get('time_s',23)
        if time_s <= 22:
            a(x,y)
        else:
            print('太晚了,赶紧睡')
    return zhang
@zzw
def zhuo(c,d):
    print('{}正在玩{}'.format(c,d))
zhuo('张三','王者荣耀',time_s = 18,f = 13)    
#张三正在玩王者荣耀
#装饰器最大的用处就是可以从外部调用函数内部的内层函数,实现隔离作用域保护内层变量的同时,还可以高效的实现某个需要装饰的功能
posted @ 2021-05-24 23:16  zzwYYYYYY  阅读(66)  评论(0)    收藏  举报