装饰器

1、什么是装饰器

装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

2、为何要用装饰器

装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能

遵循开放封闭原则:
开放:指的是对拓展功能是开放的
封闭:指的是对修改源代码是封闭的

3、如何用装饰器

需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能

def index(x,y):
    time.sleep(3)
    print('index %s %s' %(x,y))

index(111,222)

解决方案一:在函数内部增加功能

# 问题:没有修改被装饰对象的调用方式,但是修改了其源代码,失败
import time

def index(x,y):
    start=time.time()
    time.sleep(3)
    print('index %s %s' %(x,y))
    stop = time.time()
    print(stop - start)
    
index(111,222)

# 结果
index 111 222
3.0009360313415527

解决方案二:在全局空间调用index函数前后增加代码

# 问题:没有修改被装饰对象的调用方式,也没有修改了其源代码,并且加上了新功能,但是没调用一次index函数就要写一次,代码冗余
import time

def index(x,y):
    time.sleep(3)
    print('index %s %s' %(x,y))

start=time.time()
index(111,222)
stop=time.time()
print(stop - start)

start=time.time()
index(33,44)
stop=time.time()
print(stop - start)

start=time.time()
index(55,66)
stop=time.time()
print(stop - start)

# 结果
index 111 222
3.0004470348358154
index 33 44
3.000368118286133
index 55 66
3.0004358291625977

解决方案三:将功能代码封装进函数

# 问题:解决了方案二代码冗余问题,但带来一个新问题即函数的调用方式改变了
import time

def index(x,y):
    time.sleep(3)
    print('index %s %s' %(x,y))

def wrapper():
    start=time.time()
    index(111,222)
    stop=time.time()
    print(stop - start)

wrapper()

# 结果
index 111 222
3.000734806060791

大方向:如何在方案三的基础上不改变函数的调用方式

解决方案三的优化一:在wrapper函数中加入参数

# 方案三的优化一:可以给index灵活传参,新的问题是,只能给index增加功能
import time

def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

def wrapper(*args,**kwargs):
    start=time.time()
    index(*args,**kwargs) # index(3333,z=5555,y=44444)
    stop=time.time()
    print(stop - start)

wrapper(1,2,3)

# 结果
index 1 2 3
3.00087833404541

解决方案三的优化二:给wrapper函数变成闭包函数

# 方案三的优化二:外层函数传入参数,将wrapper内的函数调用变成外层函数的参数,在优化一的基础上把被装饰对象写活了,原来只能装饰index,新的问题是原函数有返回值,调用函数的时候无返回值
import time

def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)
	res 123
def outter(func):
    # func = index的内存地址
    def wrapper(*args,**kwargs):
        start=time.time()
        func(*args,**kwargs) # index的内存地址()
        stop=time.time()
        print(stop - start)
    return wrapper

index=outter(index) # index=wrapper的内存地址
home=outter(home) # home=wrapper的内存地址

index(2,3,4)
home('yu')
res=home()
print(res)
# 结果
index 2 3 4
3.0005781650543213
welcome yu to home page
2.0014724731445312
None

解决方案三的优化三:给函数wrapper以返回值

# 方案三的优化三:将wrapper做的跟被装饰对象一模一样,以假乱真
import time

def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)

def outter(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper



# 偷梁换柱:home这个名字指向的wrapper函数的内存地址
home=outter(home)

res=home('yu') # res=wrapper('yu')
print('返回值--》',res)

# 结果
welcome egon to home page
2.000577926635742
返回值--》 None
语法糖:让你开心的语法
import time
# 装饰器
def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper

# 在被装饰对象正上方的单独一行写@装饰器名字
@timmer # index=timmer(index)
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

@timmer # home=timmer(ome)
def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)

index(x=1,y=2,z=3)
home('yu')

# 结果
index 1 2 3
3.0008275508880615
welcome yu to home page
2.0004775524139404
# 总结无参装饰器模板
def outter(func):
    def wrapper(*args,**kwargs):
        # 1、调用原函数
        # 2、为其增加新功能
        res=func(*args,**kwargs)
        return res
    return wrapper
```python
#偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数
#        所以应该将wrapper做的跟原函数一样才行
def outter(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs) # res=index(1,2)
        return res
    return wrapper
@outter # index=outter(index)
def index(x,y):
    """这个是主页功能"""
    print(x,y)

print(index.__name__) # 指向的是wrapper的内存地址的名字
print(index.__doc__)  # wrapper内没有注释功能 所以返回None

# 结果
wrapper
None
1 2

def outter(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs) # res=index(1,2)
        return res
    # 手动将原函数的属性赋值给wrapper函数
    # 1、函数wrapper.__name__ = 原函数.__name__
    # 2、函数wrapper.__doc__ = 原函数.__doc__
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper
@outter # index=outter(index)
def index(x,y):
    """这个是主页功能"""
    print(x,y)

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

# 结果
index
这个是主页功能
1 2

def outter(func):
    @wraps(func) # 将原函数的属性赋值给wrapper函数 装饰器作用
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs) # res=index(1,2)
        return res
    return wrapper
@outter # index=outter(index)
def index(x,y):
    """这个是主页功能"""
    print(x,y)

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

# 结果
index
这个是主页功能
1 2