Python 从类外访问self

Python 从类外访问self,python,decorator,Python,Decorator,我试图在类中的某些方法上实现一个decorator,这样,如果尚未计算值,该方法将计算该值,否则它将只返回预计算的值,该值存储在实例defaultdict中。我似乎不知道如何从类外声明的decorator内部访问实例defaultdict。关于如何实现这一点有什么想法吗 以下是导入(用于工作示例): 这是我的装饰师: class CalcOrPass: def __init__(self, func): self.f = func #if the value i

我试图在类中的某些方法上实现一个decorator,这样,如果尚未计算值,该方法将计算该值,否则它将只返回预计算的值,该值存储在实例
defaultdict
中。我似乎不知道如何从类外声明的decorator内部访问实例
defaultdict
。关于如何实现这一点有什么想法吗

以下是导入(用于工作示例):

这是我的装饰师:

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中声明了decorator,尝试与decorator传递多个参数(显然你不能这么做),并在我的转椅上旋转,同时尝试将纸飞机扔进我的蝎子坦克。任何帮助都将不胜感激

定义装饰器的方式会丢失目标对象信息。改用函数包装器:

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
wrapps
来自
functools
,这里不需要,但非常方便


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

defaultdict(lambda: None)

但是,既然您正在测试密钥是否存在,那么您应该选择一个简单的
dict

当您的函数被定义时,您不能做您想做的事情,因为它是未绑定的。以下是一种在运行时以通用方式实现它的方法:

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
简短的解释:

  • 我们的decorator定义了
    \uuuuu get\uuuuu
    (因此被称为描述符)。虽然属性访问的默认行为是从对象的字典中获取它,但如果定义了描述符方法,Python将调用它
  • 对象的情况是
    object.\uuuu getattribute\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    将类似于
    b类的访问转换为
    type(b)。\uuuuuuu dict\uuuuuuu
  • 通过这种方式,我们可以从描述符的参数访问绑定类及其类型
  • 然后我们创建一个新的CalcOrPass对象,它现在装饰(包装)一个绑定方法,而不是旧的未绑定函数
  • 请注意新的样式类定义。我不确定这是否适用于老式类,因为我还没有尝试过;只是不要用那些。:)但是,这对函数和方法都有效
  • “旧的”修饰函数的情况留作练习

我没有很好的答案回答您的问题,但是
defaultdict
不应该住在装饰器中,而不是
SimpleData
实例中吗?我不明白为什么
SimpleData
应该知道decorator提供的实现细节(这也可以解决您的问题)。嗯,这可能行得通。但是,我需要为每个SimpleData实例使用不同的默认dict…另一方面,请注意:在Python2中编码时,始终从
object
继承类。如果不这样做,将给您提供“老式”类,这些类的行为可能与预期的不同。您需要的是一个简单的记忆装饰器。不要重新发明轮子——使用已经编写好的轮子,或者至少用标准的、更简单的方法——就像你没有选择的答案一样。如果你有Python 2.6或更高版本,你也可以使用类装饰器,然后检查所有的类属性并为它们绑定缓存函数(或者寻找由另一个装饰程序标记的,并仅绑定它们)。但这是一个巨大的痛苦。:-@torek:缓存功能?这只是一个记忆功能吗?事实证明,这一个比另一个答案更难实现。话虽如此,这个答案正是我想要的。@JoelCornett:是的,尽管“记忆化”倾向于用于带参数的函数,在这个特定的例子中,参数(
self.data
)是隐含的且不变的。
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