为类成员函数记住包装器以返回部分值

时间:2015-02-24 19:17:16

标签: python wrapper memoization functools

我正在使用来自here的memoize配方,并稍微修改了一个返回2个值的函数。我使用这个包装器来创建两个单独的函数,它们分别返回第一个和第二个值,但是函数评估被缓存,这样当使用相同的参数调用返回的函数时,就没有开销。这是这个包装器的代码。

def memoize(obj, cache_limit=10):
    '''
    This function caches the return value each time it is called. partial() is used to return the appropriate value.
    Cache size is limited to 10
    See here for details on this design pattern: https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
    '''
    cache = obj.cache = {}
    key_cache = collections.deque()

    @functools.wraps(obj)
    def memoizer(which, *args, **kwargs):
        key = str(args)
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
            key_cache.append(key)
            if len(key_cache) >= cache_limit:
                del cache[key_cache.popleft()]
        return cache[key][which]
    return functools.partial(memoizer, 0), functools.partial(memoizer, 1)

现在,我试图在函数f上使用它,这在类中以这种方式定义:

class test_function:
    def __init__(self):
        ''''''

    def f(self,x):
        return 2*x, 3*x

我这样称呼它

a = test_function()
f_v1, f_v2 = memoize(a.f)

如果成功f_v1(x)将返回2xf_v2(x)将返回3x。但这失败并出现错误:

AttributeError: 'instancemethod' object has no attribute 'cache'

如果函数在类之外声明,我的代码可以正常工作。我错过了什么?我正在使用Python 2.7

1 个答案:

答案 0 :(得分:1)

方法是一种不同于函数的对象(一个instancemethod对象,正如错误消息所示;此类型可用作types.MethodType)。与函数对象不同,实例方法没有__dict__,因此您无法在它们上设置任意属性;您无法obj.someMethod.someAttribute = "blah"在方法上创建名为someAttribute的自定义属性。

我不清楚为什么要将缓存存储在对象上,因为你实际上从未实际访问它。如果您只使用局部变量cache,它将保存在闭包中并且可以正常工作:

def memoize(obj, cache_limit=10):
    '''
    This function caches the return value each time it is called. partial() is used to return the appropriate value.
    Cache size is limited to 10
    See here for details on this design pattern: https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
    '''
    cache = {}
    key_cache = collections.deque()

    @functools.wraps(obj)
    def memoizer(which, *args, **kwargs):
        key = str(args)
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
            key_cache.append(key)
            if len(key_cache) >= cache_limit:
                del cache[key_cache.popleft()]
        return cache[key][which]
    return functools.partial(memoizer, 0), functools.partial(memoizer, 1)

>>> a = test_function()
... f_v1, f_v2 = memoize(a.f)
>>> f_v1(2)
4
>>> f_v2(2)
6