4

我正在尝试在类中的某些方法上实现装饰器,以便如果尚未计算该值,则该方法将计算该值,否则它将仅返回存储在实例中的预计算值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 中声明装饰器,尝试使用装饰器传递多个参数(显然你不能这样做),并在我的转椅上旋转,同时试图将纸飞机扔进我的蝎子坦克。任何帮助,将不胜感激!

4

2 回答 2

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

wraps是从functools这里不是绝对必要的,但非常方便。


旁注:defaultdict采用工厂函数参数:

defaultdict(lambda: None)

但是由于您无论如何都在测试密钥是否存在,所以您应该更喜欢简单的dict.

于 2012-04-15T21:04:12.560 回答
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 对象,它现在装饰(包装)一个绑定方法,而不是旧的未绑定函数。
  • 请注意新的样式类定义。我不确定这是否适用于旧式课程,因为我还没有尝试过;只是不要使用那些。:) 但是,这对函数和方法都有效。
  • “旧”装饰功能会发生什么,留作练习。
于 2012-04-15T21:23:54.680 回答