定义一个属性可由用户修改的装饰器:
在前面的介绍中使用装饰器来包装函数,这一章来介绍下如何让用户调整装饰器的属性。
首先来看下代码:
from functools import wraps,partial
import logging
def attach_wrapper(obj,func=None):
if func is None:
return partial(attach_wrapper,obj)
print(obj)
setattr(obj,func.__name__,func)
def logged(level,name=None,message=None):
def decorate(func):
logname=name if name else func.__module__
log=logging.getLogger(logname)
logmsg=message if message else func.__name__
@wraps(func)
def wrapper(*args,**kwargs):
log.log(level,logmsg)
return func(*args,**kwargs)
@attach_wrapper(wrapper)
def set_level(newlevel): #访问器函数
nonlocal level
level=newlevel
@attach_wrapper(wrapper)
def set_message(newmsg): #访问器函数
nonlocal logmsg
logmsg=newmsg
return wrapper
return decorate
@logged(logging.DEBUG)
def add(x,y):
return x+y
@logged(logging.CRITICAL,'example')
def spam():
print('Spam!')
在上面的代码中set_level和set_message就是访问器函数,这个访问器函数被attach_wrapper所装饰。我们通过断点调试来看下每步的执行过程:
首先执行add函数的装饰功能
@logged(logging.DEBUG)
def add(x,y):
return x+y
此时进入logged函数
在第一次运行set_level的时候,此时传入装饰器的参数obj为add函数,func为None
此时由于func为None, 执行partial函数,partial是中偏函数,函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。
例如下面的例子:
In [12]: plus = partial(add,100)
In [13]: plus(9)
Out[13]: 109
这里讲下attach_wrapper的用法:
1 首次执行的时候是attach_wrapper(wrapper)(set_level)。此时wrapper是add函数。
2 经过执行partial后,变成了attach_wrapper(wrapper,set_level)
最后通过调用setattr(obj,func.__name__,func)执行属性,此时obj=add,func.__name__为set_level,func为set_level函数。执行完成后add函数就有了一个属性set_level。也就是add.set_level=set_level(). 至次,我们就通过装饰器的方法将set_level函数添加到了add的属性里面。同理set_message。
关于这种功能的使用有2点需要注意:
1 在set_level和set_message函数中使用了nonlocal关键字,使用nonlocal关键字,那么使用的level参数就是在logged中使用的level参数。
2 对于这种装饰器的使用方法,必须使用@wraps(func)的方法。这样才能保持被装饰函数的独立性。否则add和spam就被成了wrapper函数。
我们在attach_wrapper添加打印来看下实际的调用结果
def attach_wrapper(obj,func=None):
print("attach_wrapper:"+str(func))
if func is None:
return partial(attach_wrapper,obj)
print(obj)
setattr(obj,func.__name__,func)
return func
调用结果:
attach_wrapper:None
attach_wrapper:<function logged.<locals>.decorate.<locals>.set_level at 0x7f2186a94730>
<function add at 0x7f2186a946a8>
attach_wrapper:None
attach_wrapper:<function logged.<locals>.decorate.<locals>.set_message at 0x7f2186a947b8>
<function add at 0x7f2186a946a8>
attach_wrapper:None
attach_wrapper:<function logged.<locals>.decorate.<locals>.set_level at 0x7f2186a94950>
<function spam at 0x7f2186a948c8>
attach_wrapper:None
attach_wrapper:<function logged.<locals>.decorate.<locals>.set_message at 0x7f2186a949d8>
<function spam at 0x7f2186a948c8>
最后来看下最终的调用:
if __name__=="__main__":
logging.basicConfig(level=logging.DEBUG)
add.set_level(logging.WARNING)
print(add(2, 3))
add.set_message("Add called")
print(add(3,4))
运行结果:
5
WARNING:__main__:add
7
WARNING:__main__:Add called