disable `functools.lru_cache` from inside function
Asked Answered
C

3

7

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?

Cosper answered 11/6, 2019 at 12:59 Comment(4)
You can't handle that from inside the function - if it's a cache hit, the function never even gets invoked.Misha
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 resultLimann
RomanPerekhest why would I want to return the function object instead of the function results?Cosper
@TheStrangeQuark, there are various scenarios of generating objects. No one compels you to extend a "home" code, it's up to youLimann
G
13

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
Graham answered 11/6, 2019 at 13:7 Comment(0)
A
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)
Aegisthus answered 11/6, 2019 at 13:13 Comment(0)
G
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)


Gomez answered 4/4 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.