8

I want to have a function that can use functools.lru_cache, but not by default. I am looking for a way to use a function parameter that can be used to disable the lru_cache. Currently, I have a two versions of the function, one with lru_cache and one without. Then I have another function that wraps these with a parameter that can be used to control which function is used

def _no_cache(foo):
    print('_no_cache')
    return 1


@lru_cache()
def _with_cache(foo):
    print('_with_cache')
    return 0


def cache(foo, use_cache=False):
    if use_cache:
        return _with_cache(foo)
    return _no_cache(foo)

Is there a simpler way to do this?

4
  • 1
    You can't handle that from inside the function - if it's a cache hit, the function never even gets invoked. Commented Jun 11, 2019 at 13:01
  • 1
    all you can invent here properly - is just a Factory function which will return cached or uncached function object (depending on parameter), not the a function result Commented Jun 11, 2019 at 13:06
  • RomanPerekhest why would I want to return the function object instead of the function results? Commented Jun 11, 2019 at 13:14
  • @TheStrangeQuark, there are various scenarios of generating objects. No one compels you to extend a "home" code, it's up to you Commented Jun 11, 2019 at 13:46

3 Answers 3

14

You can't disable the cache from inside the decorated function. However, you can simplify your code a bit by accessing the function directly via the __wrapped__ attribute.

From the documentation:

The original underlying function is accessible through the __wrapped__ attribute. This is useful for introspection, for bypassing the cache, or for rewrapping the function with a different cache.

Demo:

from functools import lru_cache

@lru_cache()
def f(arg):
    print(f"called with {arg}")
    return arg    

def call(arg, use_cache=False):
    if use_cache:
        return f(arg)
    return f.__wrapped__(arg)

call(1)
call(1, True)
call(2, True)
call(1, True)

Output:

called with 1
called with 1
called with 2
Sign up to request clarification or add additional context in comments.

Comments

3

For inspection, you can use cache_info() method of wrapped function:

from functools import lru_cache

@lru_cache()
def my_function(foo):
    return foo * 2

def cache(foo, use_cache=False):
    if use_cache is False:
        return my_function.__wrapped__(foo)
    return my_function(foo)

print(cache(10, use_cache=True))    # cache miss
print(cache(10, use_cache=True))    # cache hit
print(cache(10, use_cache=False))   # bypass
print(cache(20, use_cache=False))   # bypass

print(my_function.cache_info())     # cache size=1, hit=1, miss=1

Prints:

20
20
20
40
CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)

Comments

0

You may use a second decorator that implements the cache control, and removes that kwarg.

from functools import wraps
from cachetools import TTLCache, cached

def cache_control(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        use_cache = kwargs.get('use_cache', True)
        if use_cache:
            return func(*args, **{k: v for k, v in kwargs.items() if k != 'use_cache'})
        else:
            return func.__wrapped__(*args, **{k: v for k, v in kwargs.items() if k != 'use_cache'})
    return wrapper

And invoke the original function double decorated:

mycache = TTLCache(maxsize=settings.CACHE_SIZE, ttl=settings.CACHE_TTL)

@cache_control
@cached(mycache)
def my_function()
  pass

# call with cache
my_function(use_cache=True)

#call without cache
my_function(use_cache=False)


Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.