Skip to content

Instantly share code, notes, and snippets.

@Feuermurmel
Last active May 3, 2017 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Feuermurmel/26c8a10ee2c9c7fc3cbacc7dd84ecfba to your computer and use it in GitHub Desktop.
Save Feuermurmel/26c8a10ee2c9c7fc3cbacc7dd84ecfba to your computer and use it in GitHub Desktop.
@method_lru_cache() decorator
from functools import lru_cache, wraps
from itertools import count
_method_lru_cache_sequence = count()
def method_lru_cache(*lru_args, **lru_kwargs):
"""
Like `functools.lru_cache` which can be used on methods of a class
without keeping global references to instances passed via `self`.
Uses the same arguments as `lru_cache`.
"""
lru_decorator = lru_cache(*lru_args, **lru_kwargs)
def decorator(func):
# Catch hard to find mistake.
if not callable(func):
raise TypeError(
'`func` is not callable. Did you write `@method_lru_cache` '
'instead of `@method_lru_cache()`?')
# Make sure that property names are unique event if a method name is
# unavailable (e.g. for lambda expressions).
sequence = next(_method_lru_cache_sequence)
property_name = '__property_cache_{}_{}'.format(func.__name__, sequence)
# Create an instance implementing the descriptor protocol so that we
# can return the caching function returned by lru_decorator() on
# attribute access instead of having to return a wrapper function
# which finds or creates the caching function when called.
# This allows a caller to access attributes on the caching function.
class Wrapper:
def __call__(self, instance, *args, **kwargs):
# We only support decorating Python functions, which do not
# care about the value passes as owner.
return self.__get__(instance, None)(*args, **kwargs)
def __get__(self, instance, owner):
if instance is None:
# We can't return the wrapped function here directly,
# defer lookup to when __call__() is called.
return self
cached_func = getattr(instance, property_name, None)
if cached_func is None:
cached_func = lru_decorator(func.__get__(instance, owner))
setattr(instance, property_name, cached_func)
return cached_func
# Wrap the instance instead of the class because a class' __dict__ is
# a read-only wrapper.
return wraps(func)(Wrapper())
return decorator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment