[Python-ideas] Fwd: Using functools.lru_cache only on some arguments of a function

Bill Winslow bunslow at gmail.com
Sun Jan 10 03:27:36 EST 2016


Sorry for the late reply everyone.

I think relying on closures, while a solution, is messy. I'd still much
prefer a way to tell lru_cache to merely ignore certain arguments. I'll use
some variant of the
storing-the-partial-progress-as-an-attribute-on-the-cached-recursive-function
method (either with the cached-recursive hidden from the top level via the
try/except stuff, or with a more simple wrapper function).

I've further considered my original proposal, and rather than naming it
"arg_filter", I realized that builtins like sorted(), min(), max(), etc all
already have the exact same thing -- a "key" argument which transforms the
elements to the user's purpose. (In the sorted/min/max case, it's called on
the elements of the argument rather than the argument itself, but it's
still the same concept.) So basically, my original proposal with renaming
from arg_filter to key, is tantamount to extending the same functionality
from sorted/min/max to lru_cache as well. As has been pointed out, my own
use case is almost certainly *not* the only use case. The implementation
and interface are both simple, and simpler than the alternatives which I'll
rely on for now (wrappers and closures, or worse, global singletons etc). I
would still like to see it in the stdlib in the future. I've appended a
largely similar patch with the proposed additions (there's some internal
variable renaming to avoid confusion, resulting in a longer diff).

Thanks again for all the input.

-Bill

-----------------------------------------------------------------------------------------------------------------
https://hg.python.org/cpython/file/3.5/Lib/functools.py


diff functools.py.orig functools.py
363c363
< def _make_key(args, kwds, typed,
---
> def _make_key(args, kwds, typed, key,
377c377,379
<     key = args
---
>     if key is not None:
>         args, kwds = key(args, kwds)
>     cache_key = args
380c382
<         key += kwd_mark
---
>         cache_key += kwd_mark
382c384
<             key += item
---
>             cache_key += item
384c386
<         key += tuple(type(v) for v in args)
---
>         cache_key += tuple(type(v) for v in args)
386,389c388,391
<             key += tuple(type(v) for k, v in sorted_items)
<     elif len(key) == 1 and type(key[0]) in fasttypes:
<         return key[0]
<     return _HashedSeq(key)
---
>             cache_key += tuple(type(v) for k, v in sorted_items)
>     elif len(cache_key) == 1 and type(cache_key[0]) in fasttypes:
>         return cache_key[0]
>     return _HashedSeq(cache_key)
391c393
< def lru_cache(maxsize=128, typed=False):
---
> def lru_cache(maxsize=128, typed=False, key=None):
400a403,407
>     If *key* is not None, it must be a callable which acts on the
arguments
>     passed to the function. Its return value is used in place of the
actual
>     arguments. It works analogously to the *key* argument to the builtins
>     sorted, max, and min.
>
421a429,431
>     if key is not None and not callable(key):
>         raise TypeErrpr('Expected key to be a callable')
>
423c433,434
<         wrapper = _lru_cache_wrapper(user_function, maxsize, typed,
_CacheInfo)
---
>         wrapper = _lru_cache_wrapper(user_function, maxsize, typed, key,
>                                      _CacheInfo)
428c439
< def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
---
> def _lru_cache_wrapper(user_function, maxsize, typed, key, _CacheInfo):
456,457c467,468
<             key = make_key(args, kwds, typed)
<             result = cache_get(key, sentinel)
---
>             cache_key = make_key(args, kwds, typed, key)
>             result = cache_get(cache_key, sentinel)
462c473
<             cache[key] = result
---
>             cache[cache_key] = result
471c482
<             key = make_key(args, kwds, typed)
---
>             cache_key = make_key(args, kwds, typed, key)
473c484
<                 link = cache_get(key)
---
>                 link = cache_get(cache_key)
487c498
<                 if key in cache:
---
>                 if cache_key in cache:
496c507
<                     oldroot[KEY] = key
---
>                     oldroot[KEY] = cache_key
513c524
<                     cache[key] = oldroot
---
>                     cache[cache_key] = oldroot
517,518c528,529
<                     link = [last, root, key, result]
<                     last[NEXT] = root[PREV] = cache[key] = link
---
>                     link = [last, root, cache_key, result]
>                     last[NEXT] = root[PREV] = cache[cache_key] = link

On Wed, Dec 30, 2015 at 11:10 PM, Michael Selik <mike at selik.org> wrote:

> On Tue, Dec 29, 2015 at 2:14 AM Franklin? Lee <
> leewangzhong+python at gmail.com> wrote:
>
>> On Sat, Dec 12, 2015 at 1:34 PM, Michael Selik <mike at selik.org> wrote:
>> > On Fri, Dec 11, 2015, 8:20 PM Franklin? Lee <
>> leewangzhong+python at gmail.com>
>> > wrote:
>>
> > This whole thing is probably best implemented as two separate functions
>> > rather than using a closure, depending on how intertwined the code
>> paths are
>> > for the shortcut/non-shortcut versions.
>>
>> I like the closure because it has semantic ownership: the inner
>> function is a worker for the outer function.
>>
>
> True, a closure has better encapsulation, making it less likely someone
> will misuse the helper function. On the other hand, that means there's less
> modularity and it would be difficult for someone to use the inner function.
> It's hard to know the right choice without seeing the exact problem the
> original author was working on.
>
>
>> >> On Fri, Dec 11, 2015 at 8:01 PM, Franklin? Lee
>> >> <leewangzhong+python at gmail.com> wrote:
>> >> > 1. Rewrite your recursive function so that the partial state is a
>> >> > nonlocal variable (in the closure), and memoize the recursive part.
>> >
>> > I'd flip the rare-case to the except block and put the normal-case in
>> the
>> > try block. I believe this will be more compute-efficient and more
>> readable.
>>
>> The rare case is in the except block, though.
>>
>
> You're correct. Sorry, I somehow misinterpreted the comment, "# To trigger
> the exception the first time" as indicating that code path would run only
> once.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20160110/ddfb2225/attachment-0001.html>


More information about the Python-ideas mailing list