|
- # Memoize a function's return value with a least-recently-used cache
- # Based on:
- # http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/
- # with added 'destructor' functionality.
-
- import collections
- import functools
-
- def lru_cache(size = 10, onremove = None):
- """Least-recently-used cache decorator.
-
- @lru_cache(size = 10, onevict = None)
- def f(...):
- pass
-
- Given a function and arguments, memoize its return value.
- Up to 'size' elements are cached.
-
- When evicting a value from the cache, call the function
- 'onremove' with the value that's being evicted.
-
- Call f.cache_remove(...) to evict the cache entry with the given
- arguments. Call f.cache_remove_all() to evict all entries.
- f.cache_hits and f.cache_misses give statistics.
- """
-
- def decorator(func):
- cache = collections.OrderedDict() # order: least- to most-recent
-
- def evict(value):
- if onremove:
- onremove(value)
-
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- key = args + tuple(sorted(kwargs.items()))
- try:
- value = cache.pop(key)
- wrapper.cache_hits += 1
- except KeyError:
- value = func(*args, **kwargs)
- wrapper.cache_misses += 1
- if len(cache) >= size:
- evict(cache.popitem(0)[1]) # evict LRU cache entry
- cache[key] = value # (re-)insert this key at end
- return value
-
- def cache_remove(*args, **kwargs):
- """Remove the described key from this cache, if present.
- Note that if the original wrapped function was implicitly
- passed 'self', you need to pass it as an argument here too."""
- key = args + tuple(sorted(kwargs.items()))
- if key in cache:
- evict(cache.pop(key))
-
- def cache_remove_all():
- for key in cache:
- evict(cache.pop(key))
-
- wrapper.cache_hits = 0
- wrapper.cache_misses = 0
- wrapper.cache_remove = cache_remove
- wrapper.cache_remove_all = cache_remove_all
-
- return wrapper
- return decorator
|