2017-03-08 11 views
3

plenty of resources about using classes as decoratorsがありますが、のメソッドを飾るという問題を扱っていません。この質問の目標はそれを修正することです。私は自分自身で解決策を投稿しますが、もちろん他の人も自分のソリューションを投稿するよう招待されます。クラスをメソッドデコレータとして使用する


「標準」の実装は

動作しないのはなぜ標準のデコレータクラスの実装に問題がPythonが飾られ、関数のバインドされたメソッドを作成しないことです。

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 

とパラメータを取り1:

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 

このような実装では、私たちはそう、メソッドのデコレータなどの機能を使用することができます私はそれが良い習慣とみなされるべきだと思います。

関連する問題