So, I recently asked a question about memoization and got great answers, and now I want to go to the next level. After a fairly large number of search queries, I could not find a link to the implementation of the memoize decorator, which was able to cache a function that used keyword arguments. In fact, most of them simply used *args as the key to search in the cache, which would mean it would break if you wanted to memoize a function that took lists or dicts as arguments.
In my case, the first argument to the function is a unique identifier in itself, suitable for use as a key key for finding a cache, however I need the ability to use keyword arguments and still access the same cache. I mean that my_func('unique_id', 10) and my_func(foo=10, func_id='unique_id') should return the same cached result.
To do this, we need a clean and pythonic way of saying "check kwargs for which keyword this matches the first argument". Here is what I came up with:
class memoize(object): def __init__(self, cls): if type(cls) is FunctionType:
The crazy thing is that it really works. For example, if you decorate like this:
@memoize class FooBar: instances = {} def __init__(self, unique_id, irrelevant=None): print id(self)
Then, from your code, you can call either FooBar('12345', 20) or FooBar(irrelevant=20, unique_id='12345') and actually get the same FooBar instance. Then you can define a different class with a different name for the first argument, because it works in a general way (that is, the decorator does not need to know anything specific about the class that it decorates for this to work).
The problem is that this is an unholy mess; -)
This works because inspect.getcallargs returns a dictation matching specific keywords with the arguments you supply to it, so I supply it with fake arguments and then check the dict for the first argument passed.
What would be much better if such a thing even existed is an analogue of inspect.getcallargs , which returns both types of arguments, unified as a list of arguments, and not as an argument to keyword arguments. This will do something like this:
def __call__(self, *args, **kwargs): key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1] if key not in self.cls.instances: self.cls.instances[key] = self.cls(*args, **kwargs) return self.cls.instances[key]
Another way I can handle this would be to use the dict provided by inspect.getcallargs as the search key directly, but this will require a second way to make identical lines from the same hashes, which I cannot rely on (I think I would have to build the string myself after sorting the keys).
Does anyone have any thoughts on this? Do you want to call a function with keyword arguments incorrectly and cache the results? Or just really hard?