So I recently asked a question about memoization and got some great answers, and now I want to take it to the next level. After quite a bit of googling, I could not find a reference implementation of a memoize decorator that was able to cache a function that took keyword arguments. In fact, most of them simply used *args as the key for cache lookups, meaning that it would also break if you wanted to memoize a function that accepted lists or dicts as arguments.

In my case, the first argument to the function is a unique identifier by itself, suitable for use as a dict key for cache lookups, however I wanted the ability to use keyword arguments and still access the same cache. What I mean is, my_func('unique_id', 10) and my_func(foo=10, func_id='unique_id') should both return the same cached result.

In order to do this, what we need is a clean and pythonic way of saying 'inspect kwargs for whichever keyword it is that corresponds to the first argument)'. This is what I came up with:

class memoize(object):
    def __init__(self, cls):
        if type(cls) is FunctionType:
            # Let's just pretend that the function you gave us is a class.
            cls.instances = {}
            cls.__init__ = cls
        self.cls = cls
        self.__dict__.update(cls.__dict__)

    def __call__(self, *args, **kwargs):
        """Return a cached instance of the appropriate class if it exists."""
        # This is some dark magic we're using here, but it's how we discover
        # that the first argument to Photograph.__init__ is 'filename', but the
        # first argument to Camera.__init__ is 'camera_id' in a general way.
        delta = 2 if type(self.cls) is FunctionType else 1
        first_keyword_arg = [k
            for k, v in inspect.getcallargs(
                self.cls.__init__,
                'self',
                'first argument',
                *['subsequent args'] * (len(args) + len
Edit
Report