17---装饰器

一。什么是装饰器

器指的是工具,可以定义成成函数
装饰指的是为其他事物添加额外的东西点缀
合到一起的解释:
    装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

二。为何要用装饰器

开放封闭原则
    开放:指的是对拓展功能是开放的
    封闭:指的是对修改源代码是封闭的
    
装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能

三。如何用装饰器

1 需求
需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
def index(x, y):
    time.sleep(2)
    print(x, y)
    return '就是这么拽'
2 解决方式一
def index(x,y):
    # 开始时间
    start = time.time()
    time.sleep(2)
    print(f'x{x},y{y}')
    # 结束时间
    stop = time.time()
    print(f'stop-start{stop-start}')
index(1,2)
问题:调用方式没有改变但是改变了被装饰对象的源代码
3 解决方式二
start = time.time()
index(1,2)
# 结束时间
stop = time.time()
print(f'stop-start{stop-start}')
问题:虽然没有改变被装饰对象的源代码,也没有改变被装饰对象的调用方式但是,如果多次使用计算函数运行时间的功能,就会重复使用本段代码
4 解决方式三
def wrapper():
    start = time.time()
    index(1,2)
    # 结束时间
    stop = time.time()
    print(f'stop-start{stop-start}')
wrapper()
问题:通过调用wrapper函数间接调用index,所以是改变了index的调用方式
Ⅰ 对解决方式三的优化一
def wrapper(x,y):    
    # 相当于
    # x = 1
    # y = 1
    start = time.time()
    index(x,y)
    # 结束时间
    stop = time.time()
    print(f'stop-start{stop - start}')
wrapper(1,2)
未优化前问题:在间接调用index的时候实参被写死了
想调用index就是通过调用wrapper函数间接调用index,所以可以在定义wrapper的时候给wrapper函数定义和index函数相同的形参即可。
Ⅱ 对解决方式三的继续优化二
def wrapper(*args,**kwargs):
    # 相当于
    # x = 1
    # y = 1
    start = time.time()
    index(*args,**kwargs)
    # 结束时间
    stop = time.time()
    print(f'stop-start{stop - start}')
wrapper(1,2)
未优化前问题:wrapper函数的形参个数受到index的牵制,如果index函数的参数变了,那么wrapper函数在定义的时候形参也需要跟着变,不管是形参的名字还是个数

解决方法:将wrapper函数的形参定义为可变长度的形参,使用可变长度的形参:可以接收任意形式语法正确的实参,作为index函数的实参传给index函数,此时在调用wrapper函数时,实参的个数只需要参照index有几个形参即可,而且index的形参发生变化,对wrapper的定义阶段没有任何影响
Ⅲ 继续对解决方式三进行优化三
def outer(func):
    # func = index
    def wrapper(*args,**kwargs):
        # 相当于
        # x = 1
        # y = 1
        start = time.time()
        func(*args,**kwargs)
        # 结束时间
        stop = time.time()
        print(f'stop-start{stop - start}')
    # 因为本身wrapper是一个全局名称,因为要为其传值,使用闭包函数,外层 函数执行完毕后需要把wrapper再拿回全局中
    return wrapper
# 使用一个新的变量接收outer函数的返回值,即wrapper函数的内存地址
# 其实调用wrapper就相当于间接调用了index函数
# 此时变量index指向的内存地址就是wrapper函数的内存地址
# 此时,发现源代码没有进行修改,调用方式也没有发生改变
index = outer(index)
res = index(1,2)
print(res)
未优化前问题:是只能对index函数进行装饰,如果要将其他函数也加入计算时间的功能,还是需要重复代码,所以不能把内部调用的函数写死

解决方式:通过从外部传参的方式传值:即需要被装饰的函数体的内存地址(函数名)
5 解决方式四
def outer(func):
    # func = index
    def wrapper(*args,**kwargs):
        # 相当于
        # x = 1
        # y = 1
        start = time.time()
        res = func(*args,**kwargs)
        # 结束时间
        stop = time.time()
        print(f'stop-start{stop - start}')
        return res  # 在这里加上被装饰函数的返回值即可
    return wrapper
index = outer(index)
res = index(1,2)
print(res)
未优化前问题:原本的index函数是由返回值的,而经过装饰器加工的新的index没有返回值

总结:实现了不修改源代码,不改变调用方式即开放封闭原则。无参装饰器诞生。

四。总结--无参装饰器最后一个版本

# 定义一个要被装饰的函数
def index(x, y):
    print(x, y)
    return '看不惯我,又打不过我'

# 定义装饰器:如果不加任何功能的话,其实和原来的被装饰函数的执行效果是相同的,因此这个就是装饰器的一个模板,偷梁换柱,已经不是原来的被装饰的函数,只是名字相同而已,所以对某个函数进行装饰的时候直接用模板改一改即可
def outer(func):
    # func = index
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper
index = outer(index)  # index指向的是wrapper函数的内存地址
res = index(1,2)
print(res)

五。语法糖---cute语法❤

# 在被装饰函数的正上方加@装饰器名字 就可以对被装饰函数进行装饰了
# @装饰器名字 === index = outer(index)
# 装饰器:计算函数运行时间
def timer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        time.sleep(3)
        res = func(*args,**kwargs)
        stop = time.time()
        print(stop-start)
        return res
    return wrapper
#  被装饰对象
@timer
def index(x, y):
    print(x, y)
    return '看不惯我,又打不过我'
res = index(1,2)
print(res)
posted @ 2020-03-23 18:25  微信搜索-程序媛小庄  阅读(151)  评论(0编辑  收藏  举报