python——装饰器应用案例

参考视频链接:【Python 装饰器实战技巧 | Python 进阶】 https://www.bilibili.com/video/BV1Wa4y1x7Kk/?share_source=copy_web&vd_source=ce4fb67cf3ee23fd630eb4618951505b

1、使新建的函数自动加入函数队列

#案例:
'''任务队列'''
tasks = []
'''0号装饰器函数定义时,添加到任务队列'''
def task(f):
    global tasks
    tasks.append(f)
    return f

@task
def play():
    return 'playing...'

'''遍历队列'''
def action():
    for task in tasks:
        print(task,':',task())

if __name__ == '__main__':
    action()

在这里插入图片描述

2、为使结果更加直观进行如下修改:

#案例:
'''任务队列'''
tasks = []
'''0号装饰器函数定义时,添加到任务队列'''
def task(f):
    global tasks
    tasks.append(f)
    return f

@task
def play():
    return 'playing...'

'''遍历队列'''
def action():
    for task in tasks:
    	# 此处修改!!!
        print(task.__name__,':',task())

if __name__ == '__main__':
    action()

在这里插入图片描述

3、通过setattr手动为函数添加属性,使用自定义的名称

#案例:
'''任务队列'''
tasks = []
'''0号装饰器函数定义时,添加到任务队列'''
def task(f):
    global tasks
    tasks.append(f)
    # 此处修改
    setattr(f,'name',f.__name__)
    return f

@task
def play():
    return 'playing...'

'''遍历队列'''
def action():
    for task in tasks:
        # 此处修改
        print(task.name,':',task())

if __name__ == '__main__':
    action()

结果同上

4、添加更多函数:

#案例:
'''任务队列'''
tasks = []
'''0号装饰器函数定义时,添加到任务队列'''
def task(f):
    global tasks
    tasks.append(f)
    # 此处修改
    setattr(f,'name',f.__name__)
    return f

@task
def play():
    return 'playing...'

@task
def coin():
    return 'ding!'

'''遍历队列'''
def action():
    for task in tasks:
        # 此处修改
        print(task.name,':',task())

if __name__ == '__main__':
    action()

在这里插入图片描述

5、希望自定义函数名称,通过配置函数实现

#案例:
'''任务队列'''
tasks = []
# 修改为1号装饰器
def task(name=''):
    def _task(f):
        global tasks
        tasks.append(f)
        # 添加判断是否添加了配置
        if name:
            setattr(f,'name',name)
        else:
            setattr(f,'name',f.__name__)
        return f
    return _task

# 由于装饰器改变,每个装饰器都要填加()
@task()
def play():
    return 'playing...'

@task('投币')
def coin():
    return 'ding!'

'''遍历队列'''
def action():
    for task in tasks:
        print(task.name,':',task())

if __name__ == '__main__':
    action()

在这里插入图片描述

6、但是为了适应新的装饰器,需要将前面所有的@task都添加(),为了优化这一点:

#案例:
'''任务队列'''
tasks = []
# 修改为1号装饰器
def task(name=''):
    def _task(f):
        global tasks
        tasks.append(f)
        # 添加判断是否添加了配置
        if name:
            setattr(f,'name',name)
        else:
            setattr(f,'name',f.__name__)
        return f
    # 判断name是否可调用,因为当@task没有括号,name传入的就是f
    if callable(name):
        _task(name)
    return _task

# 由于装饰器改变,每个装饰器都要填加()
@task
def play():
    return 'playing...'

@task('投币')
def coin():
    return 'ding!'

'''遍历队列'''
def action():
    for task in tasks:
        print(task.name,':',task())

if __name__ == '__main__':
    action()

在这里插入图片描述

7、发现第一个函数命名失效,原因是因为调用_task时,name的值已被f覆盖,变成了函数对象。

 # 判断name是否可调用,因为当@task没有括号,name传入的就是f
    if callable(name):
    	# 重新为f和name赋值
        f,name = name,''
        _task(f)
    return _task

在这里插入图片描述

8、另一种写法:根据装饰器的原理:

# 修改为1号装饰器
def task(name=''):
    if callable(name):
    	# 如果没有()则统一在函数内为它添加一个()
        return task()(name)
    def _task(f):
        global tasks
        tasks.append(f)
        # 添加判断是否添加了配置
        if name:
            setattr(f,'name',name)
        else:
            setattr(f,'name',f.__name__)
        return f
    return _task

9、增加新功能,每次调用某个函数就在他后面计数

#案例:
'''任务队列'''
tasks = []
# 修改为1号装饰器
def task(name=''):
    if callable(name):
        return task()(name)
    def _task(f):
        global tasks
        tasks.append(f)
        # 添加判断是否添加了配置
        if name:
            setattr(f,'name',name)
        else:
            setattr(f,'name',f.__name__)
        return f
    return _task

# 添加一个包装函数,每次调用都计数
def count(f):
    counter = 0
    def wrapper(*args,**kwargs):
        # 在结果后面添加计数
        nonlocal counter
        counter += 1
        return f(*args,**kwargs)+' '+str(counter)
    return wrapper

@task
@count
def play():
    return 'playing...'

@task('投币')
def coin():
    return 'ding!'

'''遍历队列'''
def action():
    for task in tasks:
        print(task.name,':',task())

if __name__ == '__main__':
    action()

在这里插入图片描述

10、由于play函数被替换成了wrapper,函数名称出现了问题,只要将wrapper的属性替换为play的属性即可:

# 添加一个包装函数,每次调用都计数
def count(f):
    counter = 0
    def wrapper(*args,**kwargs):
        # 在结果后面添加计数
        nonlocal counter
        counter += 1
        return f(*args,**kwargs)+' '+str(counter)
    setattr(wrapper,'__name__',f.__name__)
    return wrapper

在这里插入图片描述

11、由于函数需要替换的属性比较多,这里将替换的过程封装为一个函数,改进如下:

# 添加一个包装函数,每次调用都计数
def count(f):
    counter = 0
    def wrapper(*args,**kwargs):
        # 在结果后面添加计数
        nonlocal counter
        counter += 1
        return f(*args,**kwargs)+' '+str(counter)
    update_wrapper(wrapper,f)
    return wrapper

# 这是一个工具函数,用于替换函数的属性
def update_wrapper(wrapper,wrapped):
    setattr(wrapper,'__name__',wrapped.__name__)
    setattr(wrapper,'__doc__',wrapped.__doc__)

效果等价:

12、进阶:上述函数实现的功能是替换wrapper的属性,可以用装饰器代替实现同样的效果:

# 封装函数
def update_wrapper(wrapper,wrapped):
    setattr(wrapper,'__name__',wrapped.__name__)
    setattr(wrapper,'__doc__',wrapped.__doc__)
    return wrapper

# 自动替换函数属性的装饰器,属于1类装饰器
'''传入的参数是要替换为的属性(函数)'''
def my_wrap(wrapped):
    '''传入原函数'''
    def dec(wrapper):
        '''在装饰器内将替换过程封装为函数'''
        return update_wrapper(wrapper,wrapped)
    return dec

from functools import wraps
# 添加一个包装函数,每次调用都计数
def count(f):
    counter = 0
    '''对wrapper函数用装饰器'''
    @wraps(f)
    def wrapper(*args,**kwargs):
        # 在结果后面添加计数
        nonlocal counter
        counter += 1
        return f(*args,**kwargs)+' '+str(counter)
    return wrapper

效果等价:

13、实际上python自带替换wrapper函数属性的装饰器——functools.wraps,理解原理后可以直接拿来用:

from functools import wraps
# 添加一个包装函数,每次调用都计数
def count(f):
    counter = 0
    '''对wrapper函数用装饰器'''
    @wraps(f)
    def wrapper(*args,**kwargs):
        # 在结果后面添加计数
        nonlocal counter
        counter += 1
        return f(*args,**kwargs)+' '+str(counter)
    return wrapper

效果等价:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值