2012-01-23 7 views
6

今朝は面白い問題がありました。すでにクラスメソッドであるメソッドをデコレートしますか?

# base.py 
class Base(object): 

    @classmethod 
    def exists(cls, **kwargs): 
     # do some work 
     pass 

そして、このように見えたデコレータモジュール::私はこのように見えた基本クラスを持っていた

# caching.py 

# actual caching decorator 
def cached(ttl): 
    # complicated 

def cached_model(ttl=300): 
    def closure(model_class): 
     # ... 
     # eventually: 
     exists_decorator = cached(ttl=ttl) 
     model_class.exists = exists_decorator(model_class.exists)) 

     return model_class 
    return closure 

は、ここに私のサブクラスのモデルです:

@cached_model(ttl=300) 
class Model(Base): 
    pass 

事がある、ときに私実際にModel.existsを呼び出すと、間違った引数の数について苦情が出ます!デコレータの引数を調べることは何も変わっていないことを示しています。引数は私が期待するものであり、メソッドのシグネチャと一致します。既にclassmethodで装飾されているメソッドに、さらにデコレータを追加するにはどうすればよいですか?

すべてのモデルがキャッシュされているわけではありませんが、クラスメソッドとしてすべてのモデルにexists()メソッドが存在するため、デコレータの並べ替えはオプションではありません:cached_modelはclassmethodをexistキャッシュされていないモデルのクラスメソッドはexists()ですか?

+1

だから、解決策は何ですか?それははっきりしていません。あなたが質問をそのまま放置して答えを投稿した方がずっと良かったでしょう。 – Marcin

+1

質問を投稿して自分で回答することはできますが、質問と回答は別々にしてください。 http://meta.stackexchange.com/questions/17463/can-i-answer-my-own-questions-even-those-where-i-knew-the-answer-before-asking – delnan

+0

を参照してください。 'Base'クラスの' @classmethod'です。 –

答えて

1

classmethodデコレータは実際には、特定の状況では、そのメソッドをクラスにバインドするだけでなく、私が知る限り、メソッド呼び出しの前にclassという引数を付加します。解決策は、私のクラスの装飾の閉鎖を編集した

def cached_model(ttl=300): 
    def closure(model_class): 
     # ... 
     # eventually: 
     exists_decorator = cached(ttl=ttl, cache_key=exists_cache_key) 
     model_class.exists = classmethod(exists_decorator(model_class.exists.im_func)) 

     return model_class 
    return closure 

im_funcプロパティは私がで到達し、私のキャッシングデコレータで元の関数を飾ることができます元の関数への参照を取得するために表示され、その全体を包みますclassmethodコールで混乱します。まとめ、classmethod装飾はスタック可能ではありません。引数は注入されたようです。

5

Pythonでは、メソッドが宣言されると、関数本体では、クラスが解析されて存在している場合と全く同じように機能します。演算子はその場で関数をメソッドに変換します。これは、(それがstaticmethodない場合)メソッドへの最初のパラメータを追加しない変換 - そう

>>> class A(object): 
... def b(self): 
...  pass 
... 
>>> A.b is A.b 
False 

Becasue「A」の「B」属性の各検索の別のインスタンスを生成します"メソッドオブジェクトB"

>>> A.b 
<unbound method A.b> 

>>> A.__dict__["b"] 
<function b at 0xe36230> 
しない場合、元の関数 "b" は、任意のtrasnformことなく取得することができます異なる記述子に基本的な機能をラップします @classmethod@staticmethodデコレータ

A.

から取得されたときにちょうど同じことが起こる @classmethodで飾ら機能、および値「クラス」については

は、パラメータリストに追加されます通常のインスタンスメソッドよりもclassmethodオブジェクトは、classmethodでラップされたときに関数となる記述子オブジェクトで、基本関数をラップする関数を返す '__get__'メソッドを持ち、他のすべてのものの前に「cls」パラメータを追加します。

@classmethodへのそれ以上のデコレータは、実際には関数ではなく記述子オブジェクトを処理していることを「知っている」必要があります。 - 他のデコレーターが簡単な関数で動作するように -

>>> class A(object): 
... @classmethod 
... def b(cls): 
...  print b 
... 
>>> A.__dict__["b"] 
<classmethod object at 0xd97a28> 

だから、法(スタック上の最初の1)に適用される最後の一つであることが@classmethodデコレータをできるように多くの方が簡単です( "cls"引数が最初のものとして挿入されることを知っている)。

+1

デコレータの順序を制御できない場合があります。私の場合、メソッドは常にクラスメソッドですが、いくつかのクラスだけが追加のキャッシングデコレータを持っています - それはサブクラス固有です。その場合、最初のクラスメソッドデコレータを元に戻す方法が必要です。私が終わったことのために私の答えを見てください。 –

2

Pythonに関する情報はjsbuenoありがとうございます。私はdecorating all methods of a classの場合に基づいてこの質問に対する答えを探していました。この質問への答えとjsbuenoのREPONSEを探しに基づいて、私はの線に沿って何かを集めることができました:

def for_all_methods(decorator): 

    def decorate(cls): 

     for attr in dir(cls): 
      possible_method = getattr(cls, attr) 
      if not callable(possible_method): 
       continue 

      # staticmethod 
      if not hasattr(possible_method, "__self__"): 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = staticmethod(decorated_method) 

      # classmethod 
      elif type(possible_method.__self__) == type: 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = classmethod(decorated_method) 

      # instance method 
      elif possible_method.__self__ is None: 
       decorated_method = decorator(possible_method) 

      setattr(cls, attr, decorated_method) 

     return cls 
    return decorate 

あなたは少し、これを切り倒すために使用することができ、冗長性のビットといくつかのバリエーションがあります。

0

スコット・ロブデルの偉大な答えに追加するだけで機能的な例...

messages.py

from distutils.cmd import Command 

import functools 
import unittest 

def for_all_methods(decorator): 

    def decorate(cls): 

     for attr in cls.__dict__: 
      possible_method = getattr(cls, attr) 
      if not callable(possible_method): 
       continue 

      # staticmethod 
      if not hasattr(possible_method, "__self__"): 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = staticmethod(decorated_method) 

      # classmethod 
      if type(possible_method.__self__) == type: 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = classmethod(decorated_method) 


      # instance method 
      elif possible_method.__self__ is None: 
       decorated_method = decorator(possible_method) 

      setattr(cls, attr, decorated_method) 

     return cls 

    return decorate 

def add_arguments(func): 
    """ 
    The add_arguments decorator simply add the passed in arguments 
    (args and kwargs) the returned error message. 
    """  
    @functools.wraps(func) 
    def wrapped(self, *args, **kwargs): 
     try: 
      message = func(self, *args, **kwargs) 
      message = ''.join([message, 
           "[ args:'", str(args), "'] ", 
           "[ kwargs:'", str(kwargs), "' ] " 
           ]) 
      return message 

     except Exception as e: 
      err_message = ''.join(["errorhandler.messages.MESSAGE: '", 
            str(func), 
            "(", str(args), str(kwargs), ")' ", 
            "FAILED FOR UNKNOWN REASON. ", 
            " [ ORIGINAL ERROR: ", str(e), " ] " 
            ]) 
      return err_message 

    return wrapped 



@for_all_methods(add_arguments)  
class MESSAGE(object): 
    """ 
      log.error(MSG.triggerPhrase(args, kwargs)) 

    """  
    @classmethod 
    def TEMPLATE(self, *args, **kwargs): 
     message = "This is a template of a pre-digested message." 
     return message 

使い方

from messages import MESSAGE 

if __name__ == '__main__': 
    result = MESSAGE.TEMPLATE(1,2,test=3) 
    print result 

出力

This is a template of a pre-digested message.[ args:'(1, 2)'] [ kwargs:'{'test': 3}' ] 
関連する問題