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/

Reply via email to