Python如何将装饰器定义为类
问题
你想使用一个装饰器去包装函数,但是希望返回一个可调用的实例。你需要让你的装饰器可以同时工作在类定义的内部和外部。
解决方案
为了将装饰器定义成一个实例,你需要确保它实现了__call__()和__get__()方法。例如,下面的代码定义了一个类,它在其他函数上放置一个简单的记录层:
importtypes fromfunctoolsimportwraps classProfiled: def__init__(self,func): wraps(func)(self) self.ncalls=0 def__call__(self,*args,**kwargs): self.ncalls+=1 returnself.__wrapped__(*args,**kwargs) def__get__(self,instance,cls): ifinstanceisNone: returnself else: returntypes.MethodType(self,instance)
你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:
@Profiled defadd(x,y): returnx+y classSpam: @Profiled defbar(self,x): print(self,x)
在交互环境中的使用示例:
>>>add(2,3) 5 >>>add(4,5) 9 >>>add.ncalls 2 >>>s=Spam() >>>s.bar(1) <__main__.Spamobjectat0x10069e9d0>1 >>>s.bar(2) <__main__.Spamobjectat0x10069e9d0>2 >>>s.bar(3) <__main__.Spamobjectat0x10069e9d0>3 >>>Spam.bar.ncalls 3
讨论
将装饰器定义成类通常是很简单的。但是这里还是有一些细节需要解释下,特别是当你想将它作用在实例方法上的时候。
首先,使用functools.wraps()函数的作用跟之前还是一样,将被包装函数的元信息复制到可调用实例中去。
其次,通常很容易会忽视上面的__get__()方法。如果你忽略它,保持其他代码不变再次运行,你会发现当你去调用被装饰实例方法时出现很奇怪的问题。例如:
>>>s=Spam() >>>s.bar(3) Traceback(mostrecentcalllast): ... TypeError:bar()missing1requiredpositionalargument:'x'
出错原因是当方法函数在一个类中被查找时,它们的__get__()方法依据描述器协议被调用,在8.9小节已经讲述过描述器协议了。在这里,__get__()的目的是创建一个绑定方法对象(最终会给这个方法传递self参数)。下面是一个例子来演示底层原理:
>>>s=Spam() >>>defgrok(self,x): ...pass ... >>>grok.__get__(s,Spam)> >>>
__get__()方法是为了确保绑定方法对象能被正确的创建。type.MethodType()手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。如果这个方法是在类上面来访问,那么__get__()中的instance参数会被设置成None并直接返回Profiled实例本身。这样的话我们就可以提取它的ncalls属性了。
如果你想避免一些混乱,也可以考虑另外一个使用闭包和nonlocal变量实现的装饰器,这个在9.5小节有讲到。例如:
importtypes fromfunctoolsimportwraps defprofiled(func): ncalls=0 @wraps(func) defwrapper(*args,**kwargs): nonlocalncalls ncalls+=1 returnfunc(*args,**kwargs) wrapper.ncalls=lambda:ncalls returnwrapper #Example @profiled defadd(x,y): returnx+y
这个方式跟之前的效果几乎一样,除了对于ncalls的访问现在是通过一个被绑定为属性的函数来实现,例如:
>>>add(2,3) 5 >>>add(4,5) 9 >>>add.ncalls() 2 >>>
以上就是Python如何将装饰器定义为类的详细内容,更多关于Python将装饰器定义为类的资料请关注毛票票其它相关文章!