解析代码中的装饰器的时候,Python把被装饰的函数作为第一个参数传给装饰器函数。如果想让装饰器接受其他参数就需要创建一个装饰器工厂函数,再把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
最简单的装饰器就如示例1中的register:
(示例1)
#BEGIN REGISTRATION_ABRIDGED
registry =[]defregister(func):print('running register(%s)' %func)
registry.append(func)returnfunc
@registerdeff1():print('running f1()')print('running main()')print('registry ->', registry)
f1()#END REGISTRATION_ABRIDGED
为了便于启动和禁用register执行的函数注册功能,我们可以为它提供一个可选的active参数,设置为False时,不注册被装饰函数。实现方式如示例2:
(从概念上看,这个新的register函数不是装饰器,而是装饰器工厂函数。调用它会返回真正的装饰器,这个才是应用到目标函数上的装饰器)
(示例2)
# BEGIN REGISTRATION_PARAM
registry = set() # <1>
def register(active=True): # <2>
def decorate(func): # <3>
print('running register(active=%s)->decorate(%s)'
% (active, func))
if active: # <4>
registry.add(func)
else:
registry.discard(func) # <5>
return func # <6>
return decorate # <7>
@register(active=False) # <8>
def f1():
print('running f1()')
@register() # <9>
def f2():
print('running f2()')
def f3():
print('running f3()')
# END REGISTRATION_PARAM
< 1 > registry现在是一个set对象,这样添加和删除函数的速度更快。
< 2 > register接受一个可选的关键字参数。
< 3 > decorate这个内部函数是真正的装饰器,它的参数是一个函数。
< 4 > 只有active参数的值(从闭包中获取)是True时才注册func。
< 5 > 如果active不为真,而且func在register中,那么把它删除。
< 6 > decorate是一个装饰器,必须返回一个函数。
< 7 > register是装饰器工厂函数,因此返回decorate。
< 8 > @register工厂函数必须作为函数调用,并且传入所需参数。
< 9 > 即使不传入参数,register也必须作为函数调用(@register),就是要返回真正的装饰器decorate。
这里的关键是,register()要返回decorate,然后把它应用到被装饰的函数上。
示例2中的代码在registeration_param.py模块中。导入后得到结果如下:
>>>import registration_param
running register(active=False)->decorate()
running register(active=True)->decorate()
>>>registration_param.registry
{}
这里的f2函数在registry中;但是f1不在其中,因为传给register装饰器工厂函数的参数是active=False,所以应用到f1上的decorate没有把它添加到register中。
如果不使用@句法,那么就要像常规函数那样使用register;若想把f添加到register中,则装饰f函数的句法是register()( f );如果不想添加或者把它删除的话,句法是register(active=False)( f )。示例3演示如何把函数添加到register中,以及如何从中删除函数。
示例3
>>>from registration_param import*
>>>registry #<1>
{}
>>>register()(f3) #<2>
running register(active=True)->decorate()
>>>registry #<3>
{, }
>>>register(active=False)(f2) #<4>
running register(active=False)->decorate()
>>>registry #<5>
{}
< 1 > 导入这个模块的时候,f2在registry中。
< 2 > register()表达式返回decorate,然后把它应用到f3上。
< 3 > 前一行把f3添加到registry中。
< 4 > 这次调用从registry中删除f2。
< 5 > 确认registry。
参数化装饰器的原理非常复杂,参数化装饰器通常会把被装饰的函数替换掉,而且结构上需要多一层嵌套。