Last active
May 3, 2017 15:33
-
-
Save Feuermurmel/26c8a10ee2c9c7fc3cbacc7dd84ecfba to your computer and use it in GitHub Desktop.
@method_lru_cache() decorator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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