pythonmemoization

Python - anyone have a memoizing decorator that can handle unhashable arguments?


I've been using the following memoizing decorator (from the great book Python Algorithms: Mastering Basic Algorithms in the Python Language ... love it, btw).

def memo(func):
    cache = {}
    @ wraps(func)
    def wrap(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrap

The problem with this decorator is that the dictionary-based cache means that all of my arguments must be hashable.

Does anyone have an implementation (or a tweak to this one) that allows for unhashable arguments (e.g. dictionaries)?

I know that the lack of a hash value means that the question of "is this in the cache?" becomes non-trivial, but I just thought I'd ask.

===EDITED TO GIVE CONTEXT===

I am working on a function that returns a Parnas-style "uses hierarchy" given a dictionary of module: dependencies. Here's the setup:

def uses_hierarchy(requirements):
    """
    uses_hierarchy(requirements)

    Arguments:
    requirements - a dictionary of the form {mod: list of dependencies, }

    Return value:
    A dictionary of the form {level: list of mods, ...}

    Assumptions:
    - No cyclical requirements (e.g. if a requires b, b cannot require a).
    - Any dependency not listed as a mod assumed to be level 0.

    """

    levels = dict([(mod, _level(mod, requirements))
                   for mod in requirements.iterkeys()])
    reversed = dict([(value, []) for value in levels.itervalues()])
    for k, v in levels.iteritems():
        reversed[v].append(k)
    return reversed


def _level(mod, requirements):
    if not requirements.has_key(mod):
        return 0
    dependencies = requirements[mod]
    if not dependencies:
        return 0
    else:
        return max([_level(dependency, requirements)
                    for dependency in dependencies]) + 1

So that:

>>> requirements = {'a': [],
...                 'b': [],
...                 'c': ['a'],
...                 'd': ['a','b'],
...                 'e': ['c','d'],
...                 'f': ['e']
...                 }

>>> uses_hierarchy(requirements)
{0: ['a', 'b'], 1: ['c', 'd'], 2: ['e'], 3: ['f']}

_level is the function I want to memoize to make this setup more scalable. As implemented without memoization, it calculates the level of dependencies multiple times (e.g. 'a' is calculated 8 times I think in the example above).

Thanks,

Mike


Solution

  • Here is the example in Alex Martelli Python Cookbook that show how to create a memoize decorator using cPickle for function that take mutable argument (original version) :

    import cPickle
    
    class MemoizeMutable:
        def __init__(self, fn):
            self.fn = fn
            self.memo = {}
        def __call__(self, *args, **kwds):
            import cPickle
            str = cPickle.dumps(args, 1)+cPickle.dumps(kwds, 1)
            if not self.memo.has_key(str): 
                print "miss"  # DEBUG INFO
                self.memo[str] = self.fn(*args, **kwds)
            else:
                print "hit"  # DEBUG INFO
    
            return self.memo[str]
    

    Here is a link.

    EDIT: Using the code that you have given and this memoize decorator :

    _level = MemoizeMutable(_level)
    
    requirements = {'a': [],
                   'b': [],
                   'c': ['a'],
                   'd': ['a','b'],
                   'e': ['c','d'],
                   'f': ['e']
                     }
    
    print uses_hierarchy(requirements)
    

    i was able to reproduce this:

    miss
    miss
    hit
    miss
    miss
    hit
    miss
    hit
    hit
    hit
    miss
    hit
    {0: ['a', 'b'], 1: ['c', 'd'], 2: ['e'], 3: ['f']}