2017-03-08 157 views
3

雖然有plenty of resources about using classes as decorators,但我一直未能找到任何處理方法的問題。這個問題的目標是解決這個問題。我會發布我自己的解決方案,但當然也邀請其他人發佈他們的解決方案。使用類作爲方法裝飾器


爲什麼「標準」的實施不起作用

與標準的裝飾類實現的問題是,蟒蛇不會造成裝飾功能的綁定方法:

class Deco: 
    def __init__(self, func): 
     self.func= func 

    def __call__(self, *args): 
     self.func(*args) 

class Class: 
    @Deco 
    def hello(self): 
     print('hello world') 

Class().hello() # throws TypeError: hello() missing 1 required positional argument: 'self' 

方法裝飾者需要克服這個障礙。


要求

從前面的例子中以類,下面的事情也有望可以使用:

>>> i= Class() 
>>> i.hello() 
hello world 
>>> i.hello 
<__main__.Deco object at 0x7f4ae8b518d0> 
>>> Class.hello is Class().hello 
False 
>>> Class().hello is Class().hello 
False 
>>> i.hello is i.hello 
True 

理想的情況下,該功能的__doc__和簽名以及類似的屬性將被保留爲好。

+0

也相關:[Python裝飾最佳實踐,使用類與函數](http://stackoverflow.com/questions/10294014/python-decorator-best-practice-using-a-class-vs-a-功能) –

+0

爲什麼你需要它是一個類?裝飾者只是一個函數有什麼問題? –

+0

@PaulRooney在我的特殊情況下(我正在編寫一個GUI庫),我想在函數中存儲一些屬性(如鍵盤熱鍵,描述,類別等)以及一些函數(比如''。 start_in_new_thread()','.update_status()')。不是強制所有這些屬性到函數上,而是編寫一個包裝類並完全替換該函數。 –

回答

2

基本「什麼都不做」裝飾類:

import inspect 
import functools 
from copy import copy 


class Deco: 
    def __init__(self, func): 
     self.__self__ = None # "__self__" is also used by bound methods 

     functools.update_wrapper(self, func) 

    def __call__(self, *args, **kwargs): 
     # if bound to an object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     #== change the following line to make the decorator do something == 
     return self.__wrapped__(*args, **kwargs) 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 

     # create a bound copy 
     bound = copy(self) 
     bound.__self__ = instance 

     # update __doc__ and similar attributes 
     functools.update_wrapper(bound, self.__wrapped__) 

     # add the bound instance to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(instance, self.__wrapped__.__name__, bound) 

     return bound 

,而另一種參數:

class DecoWithArgs: 
    #== change the constructor's parameters to fit your needs == 
    def __init__(self, *args): 
     self.args = args 

     self.__wrapped__ = None 
     self.__self__ = None 

    def __call__(self, *args, **kwargs): 
     if self.__wrapped__ is None: 
      return self.__wrap(*args, **kwargs) 
     else: 
      return self.__call_wrapped_function(*args, **kwargs) 

    def __wrap(self, func): 
     # update __doc__ and similar attributes 
     functools.update_wrapper(self, func) 

     return self 

    def __call_wrapped_function(self, *args, **kwargs): 
     # if bound to an object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     #== change the following line to make the decorator do something == 
     return self.__wrapped__(*args, **kwargs) 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 

     # create a bound copy of this object 
     bound = copy(self) 
     bound.__self__ = instance 
     bound.__wrap(self.__wrapped__) 

     # add the bound decorator to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(instance, self.__wrapped__.__name__, bound) 
     return bound 

這樣的實現讓我們使用的方法裝飾以及功能,所以我認爲這應該被認爲是很好的做法。