pythondictionarypython-3.xhashable

Using @functools.lru_cache with dictionary arguments


I have a method that parses strings, and a dictionary argument provides replacements for some substrings, so it doesn't have to be mutable.

This function is called often on redundant elements, so caching would improve efficiency.

Since dict is mutable and thus not hashable, @functools.lru_cache can't decorate my function. How can I overcome this?

Bonus point if it needs only standard library classes and methods (ideally some kind of frozendict in the standard library).

PS: namedtuple only in last resort, since it would need a big syntax shift.


Solution

  • Instead of using a custom hashable dictionary, use this and avoid reinventing the wheel! It's a frozen dictionary that's all hashable.

    https://pypi.org/project/frozendict/

    Code:

    from frozendict import frozendict
    
    def freezeargs(func):
        """Convert a mutable dictionary into immutable.
        Useful to be compatible with cache
        """
    
        @functools.wraps(func)
        def wrapped(*args, **kwargs):
            args = (frozendict(arg) if isinstance(arg, dict) else arg for arg in args)
            kwargs = {k: frozendict(v) if isinstance(v, dict) else v for k, v in kwargs.items()}
            return func(*args, **kwargs)
        return wrapped
    

    and then

    @freezeargs
    @lru_cache
    def func(...):
        pass
    

    Code taken from benderv's answer.

    Note: this does not work on recursive datastructures; for example, you might have an argument that's a list, which is unhashable. You are invited to make the wrapping recursive, such that it goes deep into the data structure and makes every dict frozen and every list tuple.