'器’指的是工具/器具

'装饰’指的是为其他事物添加额外的东西点缀

装饰器指的是定义一个函数(或者类),该函数是用来装饰其他函数的(为其他函数添加额外的功能),准确的说装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能


开放封闭原则

软件的设计应该遵循开放封闭原则

开放:对拓展功能是开放的

封闭:对修改源代码是封闭的

对拓展功能开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着函数/对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

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


装饰器的实现

def index(name,age):
    time.sleep(3) # 暂停3秒
    print('name:%s,age:%s' %(name,age))
index('kinght','20')

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

import time

def index(x,y,z): # index函数源代码没有改变
    time.sleep(2)
    print('index x=%s y=%s z=%s'%(x,y,z))
    return 'index'

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

def timmer(func):
    '''计算运行时长的装饰器'''
    def wrapper(*args,**kwargs): # 可以接受任意参数
        start = time.time()  # time.time 从uninx元年(1970年1月1日)到现在的秒
        res = func(*args,**kwargs) # 传递函数的参数,并接受返回值
        stop = time.time()
        print(stop - start)
        return res # 返回被装饰函数的返回值
    return wrapper

index = timmer(index) # timmer(待测量运行时长的函数名)
res = index(1,2,3) # 使用者调用方式没有改变,但增加了程序运行计算时长的功能
print(res)
print(index.__name__) # 返回真实的函数名
home = timmer(home) # 也可以装饰其他对象
home('kinght')

偷梁换柱的要求

index的参数什么样子,wrapper的参数就应该什么样子

index的返回值什么样子,wrapper的返回值就应该什么样子

index的属性什么样子,wrapper的属性就应该什么样子(下文伪装装饰器实现)


语法糖

import time

def timmer(func):
    '''计算运行时长的装饰器'''
    def wrapper(*args,**kwargs): # 可以接受任意参数
        start = time.time()  # time.time 从uninx元年(1970年1月1日)到现在的秒
        res = func(*args,**kwargs) # 传递函数的参数,并接受返回值
        stop = time.time()
        print(stop - start)
        return res # 返回被装饰函数的返回值
    return wrapper

# 在被装饰对象的正上方单独一行写@装饰器名字,就可以省略index = timmer(index)
@timmer # 函数必须放于装饰器下方
def index(x,y,z): # index函数源代码没有改变
    time.sleep(2)
    print('index x=%s y=%s z=%s'%(x,y,z))
    return 'index'

# index = timmer(index)
index(1,2,3)

叠加多个装饰器

@deco1 # 相当于 index = deco1(deco2(deco3(index)))
@deco2 # 相当于 index = deco2(deco3(index))
@deco3 # 相当于 index = deco3(index)
def index():
    pass

@到底做了什么?

@print
def func():
    pass
# 程序结果输出 <function func at 0x000001CFFE95F040>
# 已知 @函数名 == (被装饰函数名 = 函数名(被装饰函数名))
func = print(func)

@print(123) # 名字加括号的优先级是最高的
def func():
    pass
# 函数报错:TypeError: 'NoneType' object is not callable
# @print(123) == (print(123)的返回值被@调用)
# print函数没有返回值,所以为None
# @print == @None

无参装饰器模板

# 总结:无参装饰器的基本模板
def outter(func):
    def wrapper(*args,**kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        res = func(*args,**kwargs)
        return res
    return wrapper
@outter
def index():
    print('from index')
index()

装饰器伪装及被装饰对象文档同步

__name__伪装

# 查看真实函数信息
函数.__name__
# 装饰器伪装
函数wrapper.__name__ = '原函数名.__name__'

被装饰对象文档同步

# 查看文档
print(help(函数名))
print(函数名.__doc__)
# 被装饰后由于指向的是装饰器闭包函数,所以查看的是闭包函数文档,而原来的函数文档并不能被查看
# 需要在装饰器中同步
函数wrapper.__doc__ = '原函数.__doc__'

基本手动伪装修饰器思路

# 总结:无参装饰器的基本模板
def outter(func):
    def wrapper(*args,**kwargs):
        # 1.调用原函数
        res = func(*args,**kwargs)
        # 2.为其增加新功能
        # 3.伪装代码
        wrapper.__name__ = func.__name__
        wrapper.__doc__ = func.__doc__
        return res
    return wrapper
@outter
def index():
    print('from index')
index()

自动伪装修饰器

由于__xx__太多了,手动写很容易遗漏,python中有一个包可以快速完成这一步

from functools import wraps

def outter(func):
    @wraps(func) # wrapper.__xx__ = func.__xx__
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper
@outter
def index():
    '''这是index文档'''
    print('from index')
print(index.__name__) # index
print(index.__doc__) # 这是index文档

有参装饰器

无参装饰器的形成是因为要满足偷梁换柱的要求,所以要让wrapper和被装饰对象保持一致,所以在外面套了一层,而有参装饰器又是在无参函数的外面套了一层,这次是因为语法糖不支持

# 错误演示
def outter(func,x): # 假设需要传入两个值语法糖就会报错了
    # func = 函数内存地址
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper()
@outter # outter(index) 需要两个参数,但语法糖只传入了一个参数
def index(x,y):
    print(x,y)

如果说遇到wrapper内还需要参数,我们就只能再往外套一层

案例演示

这是对index函数写登陆验证的装饰器

def auth(func):
    def wrapper(*args,**kwargs):
        name = input("your name>>>:").strip()
        passwd = input("your passwd>>>:")
        # 基于文本文件user的验证方式
        with open('user',mode='rt',encoding='utf-8') as userfile:
            for user in userfile:
                username,password = user.strip().split(":")
                if name == username and passwd == password:
                    res = func(*args,**kwargs)
                    return res
            else:
                print('your username or password error')
    return wrapper
@auth
def index(x,y):
    print('index -> %s:%s' %(x,y))
index(1,2)

但是现在有个问题,目前的验证装饰器只能适用于文本文件user,而假如需要使用到其他的函数中,其他函数需要使用例如数据库例如LDAP进行验证,这个装饰器的功能就被局限住了

由于还没有学到数据库和LDAP,为了让代码更加直观,所以将从文件中去数据的方式进行文字描述,不写具体代码了

def auth(db_type):
    def deco(func):
        def wrapper(*args,**kwargs):
            name = input('your name>>>:').strip()
            pwd = input('your passwrod>>>:').strip()
            if db_type == 'file':
                print('基于文本的验证')
                with open('user', mode='rt', encoding='utf-8') as userfile:
                    for user in userfile:
                        username, password = user.strip().split(":")
                        if name == username and pwd == password:
                            res = func(*args,**kwargs)
                            return res
                    else:
                        print('your username or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该验证方式')
        return wrapper
    return deco

# deco = auth(db_type='file')
# @deco
@auth(db_type='file')
def index(x,y):
    print('index -> %s:%s' %(x,y))
index(1,2)

@auth(db_type='mysql')
def home(x,y):
    print('home -> %s:%s' %(x,y))
home(1,2)

@auth(db_type='ldap')
def transfer(x,y):
    print('transfer -> %s:%s' %(x,y))
transfer(1,2)

我们简化看一下这个代码

def auth(db_type):
    def deco(func):
        def wrapper(*args,**kwargs):
        return wrapper
    return deco

其实就是这么些东西,auth返回deco,deco返回wrapper

@auth(db_type='file')
def index(x,y):
    print('index -> %s:%s' %(x,y))
index(1,2)

运行@auth('file')等于index = deco(index) + db_type = 'file'就等于index = wrapper(1,2) + db_type = 'file'

第三层参数可以多个

装饰器为了参数func而套了两层,还需要参数的时候,为了语法糖套了第三层,但是第三层并没有被限制可以无限套娃,所以装饰器最多三层

def auth(db_type,canshu1,canshu2):
    def deco(func):
        def wrapper(*args,**kwargs):
        return wrapper
    return deco
@auth('file',canshu1,canshu2) # 第三层可以无限套娃
def index(x,y):
    pass
index(1,2)

有参装饰器模板

# 总结:有参装饰器的基本模板
def 有参装饰器(x,y,z,....)
    def outter(func):
        def wrapper(*args,**kwargs):
            # 1.调用原函数
            # 2.为其增加新功能
            res = func(*args,**kwargs)
            return res
        return wrapper
    return outter
@有参装饰器(x,y,z,....)
def index():
    print('from index')
index()
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐