2012-04-15 17 views
3

クラス内の特定のメソッドにデコレータを実装しようとしています。値がまだ計算されていない場合、メソッドは値を計算します。そうでない場合は、これはインスタンスdefaultdictに格納されます。私はクラスの外で宣言されたデコレータの内部からインスタンスdefaultdictにアクセスする方法を理解できないようです。これを実装する方法に関するアイデア?ここでクラスの外部から自分自身にアクセスする

は(実施例用)の輸入は、次のとおりです。ここで

from collections import defaultdict 
from math import sqrt 

は私のデコレータです:

class CalcOrPass: 
    def __init__(self, func): 
     self.f = func 

    #if the value is already in the instance dict from SimpleData,   
    #don't recalculate the values, instead return the value from the dict 
    def __call__(self, *args, **kwargs): 

     # can't figure out how to access/pass dict_from_SimpleData to here :(
     res = dict_from_SimpleData[self.f.__name__] 
     if not res: 
      res = self.f(*args, **kwargs) 
      dict_from_SimpleData[self.f__name__] = res 
     return res 

そして、ここでは装飾されたメソッドを持つSimpleDataクラスです:

class SimpleData: 
    def __init__(self, data): 
     self.data = data 
     self.stats = defaultdict() #here's the dict I'm trying to access 

    @CalcOrPass 
    def mean(self): 
     return sum(self.data)/float(len(self.data)) 

    @CalcOrPass 
    def se(self): 
     return [i - self.mean() for i in self.data] 

    @CalcOrPass 
    def variance(self): 
     return sum(i**2 for i in self.se())/float(len(self.data) - 1) 

    @CalcOrPass 
    def stdev(self): 
     return sqrt(self.variance()) 

これまでのところ、私はデコレータの中に宣言しようとしました。 SimpleData、デコレータで複数の引数を渡そうとしている(明らかにこれを行うことはできません)、私のスコーピオンタンクに紙飛行機を投げようとしているときに、私のスイベルチェアで回転しています。どんな助けもありがとう!

+0

あなたの質問には良い答えはありませんが、 'SimpleData'インスタンスではなくデコレータに' defaultdict'を置くべきではありませんか?私は 'SimpleData'がデコレータからの実装の詳細であると思われるものについて知っておくべき理由(なぜならあなたの問題を解決するでしょう)を見ません。 – icecrime

+0

うまくいくかもしれません。しかし、SimpleDataのすべてのインスタンスに対して別の既定の辞書を持っている必要があります。 –

+2

サイドノート:Python 2でコーディングするとき、常に 'object'からクラスを継承します。そうしないと、期待される行動とは異なる行動をとる。 – jsbueno

答えて

4

あなたのデコレータを定義する方法は、ターゲットオブジェクト情報が失われます。代わりに、関数ラッパーを使用します。

def CalcOrPass(func): 
    @wraps(func) 
    def result(self, *args, **kwargs): 
     res = self.stats[func.__name__] 
     if not res: 
      res = func(self, *args, **kwargs) 
      self.stats[func.__name__] = res 
     return res 
    return result 

wrapsfunctoolsはなく、ここでは厳密には必要ですが、非常に便利です。


サイドノート:defaultdictは、ファクトリ関数の引数とります

defaultdict(lambda: None) 

をしかし、あなたはとにかくキーの存在をテストしていることから、簡単なdictを好む必要があります。

2

関数が定義されているときは、バインドされていないため、何もできません。ここでは、実行時に一般的な方法でそれを達成するための方法です:

class CalcOrPass(object): 
    def __init__(self, func): 
     self.f = func 

    def __get__(self, obj, type=None): # Cheat. 
     return self.__class__(self.f.__get__(obj, type)) 

    #if the value is already in the instance dict from SimpleData, 
    #don't recalculate the values, instead return the value from the dict 
    def __call__(self, *args, **kwargs): 
     # I'll concede that this doesn't look very pretty. 
     # TODO handle KeyError here 
     res = self.f.__self__.stats[self.f.__name__] 
     if not res: 
      res = self.f(*args, **kwargs) 
      self.f.__self__.stats[self.f__name__] = res 
     return res 

短い説明:

  • 当社のデコレータは__get__を定義します(したがって記述と言われています)。属性アクセスのデフォルトの動作は、オブジェクトの辞書から取得することですが、記述子メソッドが定義されていれば、Pythonはそれを代わりに呼び出します。
  • オブジェクトの場合は、object.__getattribute__type(b).__dict__['x'].__get__(b, type(b))
  • に、我々は、ディスクリプタのパラメータからバウンドクラスとその型にアクセスすることができますこの方法をb.xのようなアクセスを変換ということです。
  • 次に、古いunbound関数ではなく、バインドされたメソッドをデコレート(ラップ)する新しいCalcOrPassオブジェクトを作成します。
  • 新しいスタイルクラスの定義に注意してください。私はそれを試していないので、これは古いスタイルのクラスで動作するかどうかはわかりません。ただそれらを使用しないでください。 :)これは関数とメソッドの両方で動作します。
  • "古い"装飾された機能には何が起こりますか?
+0

素晴らしい:ソリューションと良い説明に感謝します。 –

+0

Python 2.6以降を使用している場合は、クラスデコレータを使用してすべてのクラス属性を調べ、それらのキャッシング関数をバインドすることもできます(別のデコレータによってマークされたものを探してバインドします)。しかし、それは大きな痛みです。 :-) – torek

+0

@torek:キャッシュ機能?それはちょうどメモ化機能ですか? –

関連する問題