2012-04-15 114 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

我對你的問題沒有很好的回答,但是'defaultdict'不應該存在於裝飾器而不是'SimpleData'實例中?我不明白爲什麼'SimpleData'應該知道什麼似乎是裝飾器的實現細節(這也可以解決您的問題)。 – icecrime 2012-04-15 20:35:03

+0

嗯,可能會工作。我需要爲SimpleData的每個實例都有一個不同的默認字典,但是... – 2012-04-15 20:38:24

+2

附註:在Python 2中進行編碼時,總是從'object'繼承你的類。不這樣做會給你「舊式」類有不同於預期的行爲。 – jsbueno 2012-04-15 21:03:24

回答

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__變換像b.x接入到type(b).__dict__['x'].__get__(b, type(b))
  • 這樣我們就可以從描述符的參數訪問綁定類和類型。
  • 然後我們創建一個新的CalcOrPass對象,它現在可以裝飾(包裝)綁定方法而不是舊的未綁定函數。
  • 請注意新的樣式類定義。我不確定這是否適用於舊式課程,因爲我沒有嘗試過;只是不要使用這些。 :)但是,這將適用於函數和方法。
  • 「舊」裝飾功能會發生什麼變化。
+0

太棒了:感謝您的解決方案和一個很好的解釋。 – 2012-04-15 21:54:31

+0

如果你有Python 2.6或更高版本,你也可以使用一個類裝飾器,然後遍歷所有的類屬性併爲它們綁定一個緩存函數(或者尋找由另一個裝飾器標記並綁定這些的函數)。但這是一個巨大的痛苦。 :-) – torek 2012-04-15 22:14:58

+0

@torek:緩存功能?這只是一個記憶功能嗎? – 2012-04-15 22:26:20