2017-06-16 38 views
1

在注释:Is there a decorator to simply cache function return values?将可变参数用于`lru_cache`装饰函数可能会产生什么困难?

@gerrit指出,使用可变问题,但哈希的,反对的功能与functools.lru_cache装饰:

如果我传递一个哈希的,可变的参数,第一次调用该函数后,更改 对象的值,第二次调用将返回更改后的对象,而不是原始对象。这几乎肯定是 不是用户想要的。

从我的理解,假设的可变对象的__hash__()功能手动定义散列成员变量(而不是仅仅使用对象的id()这是自定义对象的默认值),改变参数对象将改变散列,因此,第二次调用应该不应该使用缓存。

如果为可变参数正确定义了__hash__()函数,是否有任何无人参与的行为可能由使用可变参数来装饰函数?

回答

1

我的意见是错了/误导性的,不涉及到lru_cache,但任何试图创建一个高速缓存功能,更多的一般工作。

我面临需要一种高速缓存功能,对于功能工作输入和输出NumPy阵列,其是可变的,而不是可哈希。由于NumPy数组不可散列,因此我无法使用functools.lru_cache。我结束了wroting是这样的:

def mutable_cache(maxsize=10): 
    """In-memory cache like functools.lru_cache but for any object 

    This is a re-implementation of functools.lru_cache. Unlike 
    functools.lru_cache, it works for any objects, mutable or not. 
    Therefore, it returns returns a copy and it is wrong if the mutable 
    object has changed! Use with caution! 

    If you call the *resulting* function with a keyword argument 
    'CLEAR_CACHE', the cache will be cleared. Otherwise, cache is rotated 
    when more than `maxsize` elements exist in the cache. Additionally, 
    if you call the resulting function with NO_CACHE=True, it doesn't 
    cache at all. Be careful with functions returning large objects. 
    Everything is kept in RAM! 

    Args: 
     maxsize (int): Maximum number of return values to be remembered. 

    Returns: 
     New function that has caching implemented. 
    """ 

    sentinel = object() 
    make_key = functools._make_key 

    def decorating_function(user_function): 
     cache = {} 
     cache_get = cache.get 
     keylist = [] # don't make it too long 

     def wrapper(*args, **kwds): 
      if kwds.get("CLEAR_CACHE"): 
       del kwds["CLEAR_CACHE"] 
       cache.clear() 
       keylist.clear() 
      if kwds.get("NO_CACHE"): 
       del kwds["NO_CACHE"] 
       return user_function(*args, **kwds) 
      elif "NO_CACHE" in kwds: 
       del kwds["NO_CACHE"] 
      key = str(args) + str(kwds) 
      result = cache_get(key, sentinel) 
      if result is not sentinel: 
       # make sure we return a copy of the result; when a = f(); 
       # b = f(), users should reasonably expect that a is not b. 
       return copy.copy(result) 
      result = user_function(*args, **kwds) 
      cache[key] = result 
      keylist.append(key) 
      if len(keylist) > maxsize: 
       try: 
        del cache[keylist[0]] 
        del keylist[0] 
       except KeyError: 
        pass 
      return result 

     return functools.update_wrapper(wrapper, user_function) 

    return decorating_function 

在我的第一个版本,我已经省略了copy.copy()功能(这确实应该copy.deepcopy()),而导致的错误,如果我改变所产生的价值,然后回顾了cached-功能。在我添加了copy.copy()功能之后,我意识到我在某些情况下占用了内存,主要是因为我的函数计数的是对象,而不是总内存使用量,这在一般情况下在Python中是不平凡的(尽管应该很容易,如果限制为NumPy阵列)。因此,我将NO_CACHECLEAR_CACHE关键字添加到了所产生的函数中,这些函数完成了它们的名称。

编写并使用此函数之后,我明白functools.lru_cache仅适用于具有可哈希输入参数的函数的原因不止一个。任何需要使用可变参数的缓存函数的人都需要非常小心。

+0

感谢您的回答! – Jonathan