day12 装饰器

01,装饰器简介

1,什么是装饰器

’装饰’代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。
作用:在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。

2,为何使用装饰器

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。

02,装饰器的使用

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

1.无参装饰器的实现

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

import time
def index(x,y):
    time.sleep(1)
    print('index %s %s' % (x, y))
# 方式一:失败
#问题:没有修改被装饰对象的调用方式,但是修改了其源代码
import time
def index(x,y):
    start_time = time.time()
    time.sleep(1)
    print('index %s %s' % (x, y))
    stop_time = time.time()
    print(stop_time-start_time)
index(1,2)
# 方式二
# 问题:没有修改被装饰对象的调用方式,也没有修改其源代码,并加上了新功能,但是代码冗余
import time
def index(x,y):
    time.sleep(1)
    print('index %s %s' % (x, y))
start = time.time()
index(1,2)
stop = time.time()
print(stop-start)

start = time.time()
index(1,2)
stop = time.time()
print(stop-start)

start = time.time()
index(1,2)
stop = time.time()
print(stop-start)
# 方式三
# 问题:解决了代码冗余问题,但带来了一个新问题即函数的调用方式改变了
import time
def index(x,y):
    time.sleep(1)
    print('index %s %s' % (x, y))
def wrapper():
    start = time.time()
    index(1,2)
    stop = time.time()
    print(stop-start)

wrapper()
# 方式三优化
# 问题:解决了代码冗余问题,但带来了一个新问题即函数的调用方式改变了
import time
def index(x,y):
    time.sleep(1)
    print('index %s %s' % (x, y))
def wrapper(a,b):
    start = time.time()
    index(a,b)
    stop = time.time()
    print(stop-start)

wrapper(1,2)
# 方式三优化①:将index的参数写活了
import time
def index(x,y,z):
    time.sleep(1)
    print('index %s %s %s' % (x, y,z))
def wrapper(*args,**kwargs):
    start = time.time()
    index(*args,**kwargs)
    stop = time.time()
    print(stop-start)

wrapper(1,2,3)
# 方式三优化②:在①的基础上把装饰对象写活了,原来只能装饰index,现在可以装饰其他对象
import time
def index(x,y,z):
    time.sleep(1)
    print('index %s %s %s' % (x, y,z))
def home(x):
    time.sleep(2)
    print("welcome %s to home page" %x)

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

index = outter(index)
index(1,2,3)
home = outter(home)
home("zhangsan")
# 方式三优化③:将wrapper做的跟被装饰对象一模一样,以假乱真
import time
def index(x,y,z):
    time.sleep(1)
    print('index %s %s %s' % (x, y,z))
def home(x):
    time.sleep(2)
    print("welcome %s to home page" %x)
    return 1

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("zhangsan")
print("返回值:",res)

这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能,只不过需要事先执行一次outter将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index,如下

index=outter(index) #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index

至此我们便实现了一个无参装饰器outter,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=outter(index)的形式,需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@outter时就会调用outter函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

import time
def outter(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print(stop-start)
        return res
    return wrapper
@outter
def index(x,y,z):
    time.sleep(1)
    print('index %s %s %s' % (x, y,z))
@outter
def home(x):
    time.sleep(2)
    print("welcome %s to home page" %x)
    return 1

home("zhangsan")

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

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

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

index=deco3(deco2(deco1(index)))
def deco1(func1):
    def wrapper1(*args,**kwargs):
        print("from wrapper1")
        res1 = func1()
        print("end1")
        return res1
    return wrapper1
def deco2(func2):
    def wrapper2(*args,**kwargs):
        print("from wrapper2")
        res2 = func2()
        print("end2")
        return res2
    return wrapper2
def deco3(func3):
    def wrapper3(*args,**kwargs):
        print("from wrapper3")
        res3 = func3()
        print("end3")
        return res3
    return wrapper3
@deco1
@deco2
@deco3
def index():
    print("index..")
index()

运行结果:
from wrapper1
from wrapper2
from wrapper3
index..
end3
end2
end1

图片.png

2.有参装饰器的实现

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

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  
posted @ 2020-12-30 18:18  小熊渣渣  阅读(70)  评论(0)    收藏  举报