想要实现装饰器接收参数,需要创建一个装饰器工厂函数,把参数传给他,返回一个装饰器,然后再把它用在要装饰去函数上。
一个参数化的装饰器,让register装饰器具备可选注册和注销功能。通过设置一个参数,设为FALSE时,不注册被装饰去函数。
注意此时register函数不是装饰器,而是装饰器工厂函数。调用它时才会返回真正的装饰器。
示例,接收参数的注册功能装饰器
registry = set() # 使用集合,添加和删除速度更快
def register(active=True):
def decorate(func): # decorate才是装饰器
if active: # 通过闭包获取active的值,是True时才注册
registry.add(func)
else:
registry.discard(func)
return func
return decorate
@register(active=False)
def f1():
print('f1 running.')
@register()
def f2():
print('f2 running.')
def f3():
print('f3 running.')
print('main running')
print('registry ->', registry)
打印
main running
registry -> {}
扩展:
使用set集合比list添加和删除的速度更快
set.discard(x) 删除指定元素,set. remove()
方法在移除一个不存在的元素时会发生错误,而 discard()
方法不会。
以上可以看到只有f2函数被注册了,f1虽然被装饰了,但是active=False,所以f1不会被注册。
如果不使用@语法,像常规函数那样使用register,装饰f2函数就是register()(f2),装饰f1函数就是register(active=False)(f1)
一个完整的三层函数的装饰器
最外层的函数是装饰器的工厂函数。第二层才是真正的装饰器,第三层是用来包装被装饰的函数用的。
示例,一个clock装饰器,参数是格式字符串。
"""clockdeco_param.py"""
import time
import functools
FMT = '[{time_use:0.8f}s] {func_name}({parameter}) -> {result}' # 格式化的模板 (作为装饰器参数)
def clock(fmt=FMT):
"""函数执行时间,参数,结果"""
def decorate(func):
@functools.wraps(func)
def inner(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs)
time_use = time.time() - t0
func_name = func.__name__
parameter = []
if args:
parameter.append(','.join(repr(arg) for arg in args))
if kwargs:
kwargs_str = ",".join('%s=%r' % (k, v) for k, v in kwargs.items())
parameter.append(kwargs_str)
parameter = ','.join(parameter)
# print('[%0.8fs] %s(%s) -> %r' % (time_use, func_name, parameter, result))
print(fmt.format(**locals())) # 这里拆包局部变量,省去了参数字典
return result
return inner
return decorate
@clock()
def test(s, a=2):
time.sleep(1)
print('func test running')
return 'over'
test('wo', a=3)
打印
func test running
[1.01234961s] test('wo',a=3) -> over
clock是参数化装饰器的工厂函数,decorate才是真正的装饰器。
扩展:
代码中使用了locals()然后拆包,locals()里面保存了inner函数的局部变量信息,是个字典,如果打印出来就是:
{'args': ('wo',), 'kwargs': {'a': 3}, 't0': 1618408087.1784744, 'result': 'over', 'time_use': 1.0123496055603027, 'func_name': 'test', 'parameter': "'wo',a=3", 'kwargs_str': 'a=3', 'fmt': '[{time_use:0.8f}s] {func_name}({parameter}) -> {result}', 'func':
如果在其他文件中使用clockdeco_param.py模块中的clock装饰器,可以:
from clockdeco_param import clock
@clock('{func_name}: {time_use}s')
def func():pass
这样就可以随意改变输出格式:
func: 0s