I'm using a variant of the following decorator for memoization (found here):
# note that this decorator ignores **kwargs
def memoize(obj):
cache = obj.cache = {}
@functools.wraps(obj)
def memoizer(*args, **kwargs):
if args not in cache:
cache[args] = obj(*args, **kwargs)
return cache[args]
return memoizer
I'm wondering, is there a reasonable way to memoize based on both args and kwargs, particularly in cases where two function calls specified with arguments assigned differently positionally and through keyword, but have the exact same arguments?
You just have to find a good way to build a key from both
args
andkwargs
. Maybe try this:I put some print-statements into
memoizer
, just to demonstrate that it works. Output is:I'm sure I didn't tackle all corner-cases, especially if the values passed-in as kwargs (or even args) are not hashable. But maybe it can serve as a good starting-point.
This solution uses the inspect module to extract parameter names for both positional and keyword arguments. The memoization lookup is then performed on the ordered tuple of name:value pairs. It can tolerate parameters being passed as both positional and keyword arguments. If there are excess positional arguments, these are stored in the order they appear in a separate tuple.
This uses Michele Simionato's
decorator
package to ensure that function signatures are preserved. Because it inspects theargspec
of the function being memoized, it will fail if composed with decorator implementations that do not preserve theargspec
.If you are using parameters either always as positionals or always as keywords, Thorsten solution works fine. But, if you want to consider equal calls that give to the parameters the same values, indipendently of how the parameters are passed, then you have to do something more complex:
The above functions tries to map keyword arguments(and defaults) to positional and uses the resultant tuple as key. I tested it a bit and it seems to work properly in most cases. It fails when the target function also uses a
**kwargs
argument.In general, it's not possible to infer that two calls have the same parameter meaning. Consider the calls
Which of these (if any) are equivalent depends on whether the positional argument is called foo or bar: if the argument is called foo, then the first call matches the second, etc. However, the positional parameter may also have an entirely different name.
IOW, you need to reflect on the function to be called, which, in turn, may not be possible (e.g. if it is implemented in C, or is itself a wrapper that only processes *args, **kwargs).
If you want to go the reflection route, something like ndpu's response is a good start.