Steve Jorgensen wrote: > I was surprised to find, when I pass arguments to a function decorated with > `@functools.cache` in different, equivalent ways, the cache does not > recognize them as the same. > counter = itertools.count(1) > @functools.cache > def example(a, b, c=0): > return (next(counter), a, b, c) > example(1, 2) # => (1, 1, 2, 0) > example(1, b=2) # => (2, 1, 2, 0) > example(1, 2, 0) # => (3, 1, 2, 0) > When I wrote my own implementation as a coding exercise, I noticed the same > weakness while testing it and solved that by having the decorator function > get the signature of the decorated function, then use the bind method of the > signature to bind the parameter values, then call the apply_defaults method > on the bound arguments, and then finally, use the args and kwargs properties > of the bound arguments to make the cache key. > It seems like functools.cache should do the same thing. If it is undesirable > for that to be the default behavior, then it could be optional (e.g. > @functools.cache(normalize=True) ). > I have not tested to see if functools.lru_cache has the same issue. I presume > that it does, so my suggestion would apply to that as well.
After saying that, I realized that, if the behavior should be optional, then maybe it would make sense to provide another wrapper to normalize the parameters instead (see possible implementation below)? On the other hand, since the primary use of such a thing would be for caching, maybe it does make more sense to include the behavior in 'functools.cache' et al., as I originally suggested, or maybe have both. def bind_call_params(func): """ Transform a function to always receive its arguments in the same form (which are positional and which are keyword) even if its implementation is less strict than what is described by its signature. This is for use in cases where the form of in which the parameters are passed may be significant to a decorator (e.g. '@functools.cache'). """ sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) bound.apply_defaults() return func(*bound.args, **bound.kwargs) return wrapper _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/B7UC2472UBGCMO2S3NZWRTDZLJ7OOPRJ/ Code of Conduct: http://python.org/psf/codeofconduct/