06-05 装饰器

一. 储备知识

# 1. *args, **kwargs
'''
外层调用传参 --> 形参组合 -->  内层打散 --> 内层调用传参
外层函数调用传参受限于内层函数定义时参数的语法规则
'''
def index(x, y):
    print(x, y)

def wrapper(*args, **kwargs):
    index(*args, **kwargs)  # 1.wrapper传的实参是什么形式会原封不动的传到index中 2.wrapper传参受限于index定义阶段形参的规则

wrapper(1, 2)

# 2. 名称空间与作用域: 名称空间的'嵌套'关系是在函数定义阶段就产生的, 也可以说检测语法的时候确定的.

# 3. 函数对象: 可以把函数当作参数传入. 可以把函数当作返回值返回.
def index():
    return 123

def bar(func):  
    func()

bar(index)

# 4. 函数嵌套定义
'''
返回内层函数局部的内存地址到全局作用域
'''
def outer():
    def wrapper():  # wrapper在函数outer内定义, 把wrapper当作返回值返回到全局
        pass
    return wrapper

# 5. 闭包函数
'''
'闭': 指的是该函数内嵌函数
'包': 指的是改函数对外层函数.作用域名字的引用

外层函数把'值'包给内层函数
返回内层函数的内存地址到全局,打破层级限制
'''
def outer(x):  # 把值x包给wrapper函数
    # x = 1
    def wrapper():
        print(x)
    return wrapper
f = outer(1)
f()

二、装饰器介绍

1、什么是装饰器

"""
拆词分析:
	'装饰': 指的是为被装饰对象添加的新功能
	'器': 指的是工具. 也可以理解为定义一个函数
	
合到一起总结(重点): 
	装饰器指的就是定义一个函数, 用来为其他函数添加新功能.		
"""

2、为何要用装饰器?

"""
引入: 软件的设计应该遵循开放封闭原则:
	软件的设计应该遵循开放封闭原则, 即对外扩展是开放的, 对修改是封闭的.	
	对扩展开放: 对新的需求或变化, 可以对现有代码进行扩展开放.
	对修改封闭: 对象设计完成, 就可以独立完成其工作, 对修改原代码封闭.
	总结(重点): 
		开放: 拓展新功能开放
		封闭: 修改源代码封闭
		
装饰器的作用(重点): 
	不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加新功能.

使用装饰器的原因: 
	原因一: 软件包含的所有功能的源代码和调用方式, 都应该避免被修改, 否者一旦改错, 极有可能产生连锁反应, 最终导致程序奔溃.
    原因二: 上线后的软件, 新需求或者变化层出不穷, 我们必须提供扩展的可能性

装饰器使用场景:

"""

三、装饰器的实现

  • 补充: time模块下的 time.time()功能
import time

print(time.time())   # time.time()返回的值是时间戳. 以1970年作为起始, 到现在时间为结束. 单位秒
# 历史补充: 1970年是unix元年

1、无参装饰器的实现

# 需求: 不修改index源代码和调用方式的前提下为index添加统计时间的功能
  • 解决方法一: 失败. 没有修改被装饰对象的调用方式,但是修改了源代码.
import time

def index(x, y):
    start_time = time.time()

    time.sleep(1)
    print('from index', x, y)

    stop_time = time.time()
    print('index is run time:', start_time - stop_time)
    
index(1, 2)
  • 解决方法二: 失败. 没有修改被装饰对象的调用方式, 也没有修改源代码. 并且加上了新功能, 但造成了代码冗余.
import time

def index(x, y):
    time.sleep(1)
    print('from index', x, y)

start_time = time.time()
index(1, 2)
stop_time = time.time()
print('index is run time:', start_time - stop_time)

start_time = time.time()
index(3, 4)
stop_time = time.time()
print('index is run time:', start_time - stop_time)
  • 解决方法三: 失败. 解决了方案二的代码冗余问题, 但是修改了被装饰对象的调用方式.
import time

def index(x, y):
    time.sleep(1)
    print('from index', x, y)

def wrapper():
    start_time = time.time()
    index(1, 2)
    stop_time = time.time()
    print('index is run time:', start_time - stop_time)

wrapper()
  • 方案三的优化一: 将index的参数写活了
import time

def index(x, y, z):
    time.sleep(1)
    print('from index', x, y, z)

def wrapper(*args, **kwargs):
    start_time = time.time()
    index(*args, **kwargs)  # index(1, 2 3)   index(1, z=3, y=2)
    stop_time = time.time()
    print('index is run time:', start_time - stop_time)

wrapper(1, 2, 3)
wrapper(1, z=3, y=2)
  • 方案三的优化二: 在优化一的基础之上, 把被装饰对象写活了, 原来只能装饰index
import time

def index(x, y, z):
    time.sleep(1)
    print('from index', x, y, z)

def outer(func):
    # func = index
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)   # index(1, 2 3)   index(1, z=3, y=2)
        stop_time = time.time()
        print('index is run time:', start_time - stop_time)
    return wrapper

# 装饰之前index
print(index)  # <function index at 0x000002533719CEE0>
index = outer(index)  # warpper = outer(index)
# 装饰之后index
print(index)  # <function outer.<locals>.wrapper at 0x00000253371AA040>

index(1, 2, 3)  # warpper(1, 2, 3)  --> func(1, 2, 3)

index(1, z=3, y=2)  # wrapper(1, z=3, y=2) --> func(1, z=3, y=2)
  • 方案三的优化三: 在优化二的基础之上, 把被装饰对象的返回值返回. 现在实现了真正意义上的伪装.
import time

def index(x, y, z):
    time.sleep(1)
    print('from index', x, y, z)
    return x, y, z

def outer(func):
    # func = index
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)   # index(1, 2 3)   index(1, z=3, y=2)
        stop_time = time.time()
        print('index is run time:', start_time - stop_time)
        return res
    return wrapper

index = outer(index)  # warpper = outer(index)

res = index(1, 2, 3)
print(res)  # (1, 2, 3)

res = index(1, z=3, y=2)
print(res)  # (1, 2, 3)
  • @语法糖基本使用伪代码介绍
# 
"""
@print   # index = print(index)
def index(x, y):
    print('from index: ', x, y)

@None   # index = None(index)
def index(x, y):
    print('from index: ', x, y)

@print('hello')  # @None = print('hello')  --> index = None(index)
def index(x, y):
    
@名字对应的内存地址  # 重新赋值成被装饰对象原来的名字index = 名字对应的内存地址(下面被装饰对象的名字对应的内存地址index)
def index(x, y):
    print('from index: ', x, y)  
"""
  • 装饰器语法糖实现
import time

'''
func表示的是原函数: 
    func(*args, **kwargs)表示的是在调用原函数
    
res表示的是原函数的返回值:
    res
        
wrapper表示的是新函数(闭包函数): 
    调用timer以后, 通过return返回它自己在timer局部的函数名到全局, 最后可以重新赋值成原函数的函数名index=wrapper,  这个时候调用index()就是调用wrapper(), 实现了没有修改原函数调用方式.
    在原函数执行前后func(*args, **kwargs), 添加新功能, 实现了没有修改原函数代码.

原函数func可能有参数, 而这种参数是不能确定的, 所以使用2个*args, **kwargs, 实现: 传值->wrapper形参组合->func实参打散->传值
'''
def timer(func):  # index = 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不会调用正下方的函数, 而是将index的内存地址传递到timer中func=index. 执行timer中的功能, 返回wrapper函数的内存地址, 赋值一个和原函数名index同样的名字, 实现偷练换柱的功能.
@timer  # index = wrapper = timer(index)
def index():
    time.sleep(2)
    print('Welcome to the index page')
    return 100

res = index()
'''
Welcome to the index page
run time is 2.000030040740967
'''
# 打印返回值
print(res)  # 100
  • 总结: 无参装饰器模板
from functools import wraps

def 无参装饰器(func):
    # '无参装饰器'做的2件事: 1.传入被装饰对象func 2.返回装饰功能wrapper的内存地址.
    
    @wraps  # # 1. 伪装被装饰对象原属性(包括: 函数名,函数文档注释,函数帮助信息....等). 如果你不想保留原函数的属性, 可以卓情况考虑不写
    def wrapper(*args, **kwargs):
        # 'wrapper'做的2件事: 1.调用原函数func  2.为其增加新功能	
        res = func(*args, **kwargs)
        return res  # 2. 伪装返回值
    return wrapper  # 3. 伪装被装饰对象名.( 返回以后, 赋值成原函数函数名.)

@无参装饰器  # wrapper=无参装饰器(index)  # 被装饰对象=wrapper
def 被装饰对象():
    pass

被装饰对象()

2、有参装饰器的实现

  • 推理: 下面需要driver参数, 但是这个参数哪里来呢?
'''
我们所知道为函数体传参有2种:
    第一种: 直接传参
        def wrapper(driver):
            print(driver)

    第二种: 使用闭包函数, 让外层的函数把值包给里层的函数
        def outer(driver):
            def wrapper():
                print(driver)
            return wrapper  # 注意: 之前我们的wrapper功能属于全局, 需要在调用outer的时候把全本属于全局wrapper, 返回到全局
        wrapper = outer(xxx)
'''

def outer(func):
    def wrapper(*args, **kwargs):
        if driver == 'file':
            print("基于文件的认证")
        elif driver == 'mysql':
            print("基于mysql的认证")
        elif driver = 'ldap':
            print("基于ldap的认证")
        res = func(*args, **kwargs)
        return res
    return wrapper
'''
分析一: wrapper不能动: 下面wrapper函数中的*args, **kwargs是专门给func用的, 用来解决调用wrapper并传参嫁接交给func不能灵活收同样参数的问题. 如果强行使用方式一为driver传参, 会导致原函数调用方式的改变.

分析二: outer不能动: 受限于语法糖@的限制, outer函数只能有一个参数, 并且改函数只能用来接收被装饰对象的内存地址. 如果强行定义driver形参, 语法糖就变成@outer --> index = outer(func, xxx) 你的参数xxx根本就没办法传入.


分析三: 基于外面2层函数都不能动, 这个时候我们就考虑到. 既然方式一的直接传参不能使用, 就只有使用闭包函数, 把wrapper中需要的driver参数包给它.内容如下👇:
'''

  • 有参装饰器实现
# wrapper偷梁换index柱以后:
'''
index的参数什么样子, wrapper的参数就应该是什么样子   ---> 2个*args, **kwargs的用法
index的返回值是什么样子, wrapper的参数就应该是什么样子 ---> 调用原函数func以后返回的res.
index的属性是什么样子, wrapper的参数就应该是什么样子 -----> 使用functools模块中的wraps功能对装饰功能的wrapper进行使用: @warps(func) func代指原函数
'''
from functools import wraps
def auth(driver):  # 这个时候的auth可以为wrapper中提供任意类型的参数了
    def outer(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if driver == 'file':
                print("基于文件的认证")
            elif driver == 'mysql':
                print("基于mysql的认证")
            elif driver == 'ldap':
                print("基于ldap的认证")

            res = func(*args, **kwargs)
            return res

        return wrapper
    return outer  # 之前的outer是属于全局的, 我们要把它返回到它原来的位置

@auth(driver='file')  # @outer  # wrapper=outer(index) # index=wrapper
def index(x, y):
    print('from index:', x, y)

@auth(driver='mysql')    # @outer # wrapper=outer(home) # home=wrapper
def home(x, y):
    print('from home:', x, y)

@auth(driver='ldap')
def transfer(x, y):
    print("from transfer", x, y)

index(1, 2)
'''
基于文件的认证
from index: 1 2
'''

home(3, 4)
'''
基于mysql的认证
from home: 3 4
'''

transfer(5, 6)
'''
基于ldap的认证
from transfer 5 6
'''
  • 总结: 有参装饰器模板
from functools import wraps

def 有参装饰器(参数1, 参数2, ..., 参数n):  # 这里的参数是在wrapper中需要为func原函数添加新功能, 但是缺少条件提供的参数.
    def outer(func):  # func: 代指原函数
        
        @wraps(func)  # 1. 伪装被装饰对象原属性(包括: 函数名,函数文档注释,函数帮助信息....等). 如果你不想保留原函数的属性, 可以卓情况考虑不写
        def wrapper(*args, **kwargs):  
            if 参数1 == '值':
                pass
            res = func(*args, **kwargs)  # 2. 伪装返回值
            return res
        return wrapper  # 3. 伪装被装饰对象名.(返回以后, 赋值成原函数函数名,达到伪装)
    return outer

@有参装饰器(参数1, 参数2, ..., 参数n)
def 被装饰对象():  # 被装饰对象传入outer. func=被装饰对象的内存地址
    pass

3. 保留原函数的文档和函数名属性以及其它属性(实现正真意义上的伪装)

  • help, __doc__, __name__基本用法介绍:
def func(x):
    """我是func的文档注释"""
    print("from func")
    return x


print(help(func))  # 1. help即查看函数文档注释也查看函数名. 返回结果如下
'''
Help on function func in module __main__:

func(x)
    我是func的文档注释

None
'''

print(func.__doc__)  #2.  __doc__查看函数文档注释. 返回结果: 我是func的文档注释
print(func.__name__)  # 3. __name__查看函数名. 返回结果: func

  • 实现保留被装饰对象之前原函数的文档和函数名属性
# 实现方式一: 手动将原函数的属性赋值给wrapper
'''
局限性: 手动繁琐, 且原函数有许多属性, 除了本次所要添加的属性, 还有很多很多, 你不可能在使用装饰器的时候, 在每个装饰中都做这种操作.
'''
import time

def timer(func):  # index = func
    def wrapper(*args, **kwargs):
        """wrapper文档注释"""
        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


@timer  # index = wrapper = timer(index)
def index(x):
    '''index文档注释'''
    print('from index')
    return x

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

# 实现方式二: 使用functools模块下提供的装饰器wraps
'''
只需要争对你的装备器wrapper增添一层装饰器wraps, 把原有的函数的属性都赋给wrapper.用法如下👇:
'''
import time
from functools import wraps

def timer(func):
    @wraps(func)  # @xxx = wraps(func)  --> wrapper = xxx = xxx(wrapper)
    def wrapper(*args, **kwargs):
        """wrapper文档注释"""
        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  # wrapper=timer(index)  index=wrapper
def index(x):
    """index文档注释"""
    print('from index')
    return x

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

# func.__doc__与func.__name__没有赋值之前
"""
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None 
None
wrapper
"""

# func.__doc__赋值给wrapper.__doc__, func.__name__赋值给wrapper.__name__之后
"""
Help on function index in module __main__:

index(x)
    index文档注释

None
index文档注释
index
"""

4. 叠加多个装饰器的加载, 运行分析(面试: 一般你会这个,基本装饰器没问题)

"""
总结:
    叠加装饰器语法糖加载顺序(了解): 从下到上
    叠加装饰器执行顺序: 从上到下(基于每个装饰器中, 被装饰对象func调用之前, 也就是说func前面的代码)
    叠加装饰器结束顺序: 从下到上(基于每个装饰器中, 被装饰对象func调用之后, 也就是说func后面的代码)
"""
def deco1(func1):  # func1 = wrapper2的内存地址
    def wrapper1(*args, **kwargs):
        print('正在运行===>deco1.wrapper1')
        res1 = func1(*args, **kwargs)
        print("正在结束===>deco1.wrapper1")
        return res1

    return wrapper1


def deco2(func2):  # func2 = wrapper3的内存地址
    def wrapper2(*args, **kwargs):
        print('正在运行===>deco2.wrapper2')
        res2 = func2(*args, **kwargs)
        print("正在结束===>deco2.wrapper2")
        return res2

    return wrapper2


def deco3(x):
    def outer3(func3):  # func3=被装饰对象index函数的内存地址
        def wrapper3(*args, **kwargs):
            print('正在运行===>deco3.outer3.wrapper3')
            res3 = func3(*args, **kwargs)
            print("正在结束===>deco3.outer3.wrapper3")
            return res3

        return wrapper3

    return outer3


# 加载顺序: 自下而上(了解)
# 注意: python中值的传递都是引用传递, 都是内存地址的传递. 例如: index=wrapper3的内存地址
@deco1  # wrapper1=deco1(wrapper2)  # index=wrapper1
@deco2  # wrapper2=deco(wrapper3)  # index=wrapper2
@deco3(111)  # @outer3  # wrapper3=outer3(index)  # index=wrapper3
def index(x, y):
    print('from index %s:%s' % (x, y))


index(1, 2)  # wrapper1 --> wrapper2 --> wrapper3 - > index

# 执行分析:
"""
引入: 装饰器都是wrapper, 只是把这个wrapper伪装成和原函数一模一样, 最后返回的就是wrapper, 虽然你看起来执行的是原函数, 最终调用的还是wrapper.
执行顺序自上而下:
    deco1.wrapper1 --> deco1.wrapper1.func1 --> deco2.wrapper2 --> deco2.wrapper2.func2 --> deco3.wrapper3 -> deco3.wrapper3.func1 --> index
"""


# 打印结果
'''
# 执行顺序
正在运行===>deco1.wrapper1
正在运行===>deco2.wrapper2
正在运行===>deco3.outer3.wrapper3
from index 1:2

# 结束顺序
正在结束===>deco3.outer3.wrapper3
正在结束===>deco2.wrapper2
正在结束===>deco1.wrapper1
'''
  • 叠加多个装饰器练习加强:
# 需求: 不适用解释器解释执行. 按照解释器可能执行的顺序打印的print的值 (注意: deco中的wrapper又加了timer装饰器)
def timer(func):
    def wrapper(*args, **kwargs):
        print('正在运行===>deco.wrapper-1')
        res = func(*args, **kwargs)
        print("正在结束===>deco.wrapper-1")
        return res

    return wrapper

def auth(func):
    def wrapper(*args, **kwargs):
        print('正在运行===>deco.wrapper-2')
        res = func(*args, **kwargs)
        print("正在结束===>deco.wrapper-2")
        return res

    return wrapper


def deco(x):
    def outer(func):
        @timer
        def wrapper(*args, **kwargs):
            print('正在运行===>deco.outer.wrapper-3')
            res = func(*args, **kwargs)
            print("正在结束===>deco.outer.wrapper-3")
            return func

        return wrapper

    return outer

@timer
@auth
@deco(111)
def index(x, y):
    print('from index %s:%s' % (x, y))

index(1, 2)
posted @ 2020-03-23 14:44  给你加马桶唱疏通  阅读(162)  评论(0编辑  收藏  举报