Search code examples
pythondecorator

Python: How can I ignore a special parameter in functools.lru_cache?


The function that I want to cache is something like:

def a(x, time='last'):

I have deterministic behaviour for every a(x,y), except when y=='last'. So when a(x, 'last') is called, I would like to call the "real thing" and an lru_cached function for everything else.

I imagine this could be possible with my own decorator:

def my_lru_cache(func):
    def function_wrapper(*args, **kwargs):
        if kwargs is not None:
            if 'time' in kwargs:
                return func(*args, **kwargs)
            else:
                return what?!?

    return function_wrapper

Am I completely wrong? How could this be done?


Solution

  • Wrap the function in lru_cache(), then add your decorator on top and access the original uncached function via the __wrapped__ attribute, or better still, use the inspect.unwrap() function to strip the function of an arbitrary number of decorators:

    from functools import wraps
    from inspect import unwrap
    
    def bypass_cache_last_time(func):
        @wraps(func)
        def function_wrapper(*args, **kwargs):
            if not 'time' in kwargs or kwargs['time'] == 'last':
                # Bypass any additional decorators and call function directly
                return unwrap(func)(*args, **kwargs)
            else:
                return func(*args, **kwargs)
    
            return function_wrapper
    

    and use this as

    @bypass_cache_last_time
    @lru_cache()
    def some_function(x, time='last'):
        # ...
    

    The functools.wraps() decorator passes the ability to unwrap the decorator again forward, as it sets the __wrapped__ attribute on the wrapper.

    Or make your decorator apply the lru_cache() decorator itself and retain your own copy of the original function when decorating:

    def my_lru_cache(func):
        cached = lru_cache()(func)
    
        @wraps(func)
        def function_wrapper(*args, **kwargs):
            if not 'time' in kwargs or kwargs['time'] == 'last':
                # call the function directly
                return func(*args, **kwargs)
            else:
                # use the lru_cache-wrapped version
                return cached(*args, **kwargs)
    
        return function_wrapper
    

    use this as

    @my_lru_cache
    def some_function(x, time='last'):
        # ...