参考视频链接:【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
效果等价: