Problem Statement
functools.cache and functools.lru_cache are well-known for not being totally safe to apply to methods, or at least not applying in the way a naive user might expect.
Because they cache based on all arguments, including self, classes declared as follows have a “buggy” behavior:
class Foo:
@functools.cache
def bar(self) -> int:
return id(self) + 1
The bug being that Foo instances will never be GCed after Foo.bar() is called, since the cache is holding a reference to them.
This is easily solved with bespoke cache, or you can make your own fancy decorator to try to make this idea generic…
But this is all non-obvious for new users, and the solutions are all higher effort than using functools.cache. I would characterize the need as typically “an annoyance” rather than “a serious gap/fault”. The need for caching/memoizing is common enough that there is a good amount of value to be gained by making a generic solution available in the stdlib.
Proposed Solution
I suggest we add a new decorator in functools, functools.cached_method.
cached_method defaults to a cache of unbounded size, but accepts LRU-cache arguments. The rationale for this is that caching on an instance will more typically want to be unbounded than a global function cache, as the cache has a lifetime limited to the lifetime of the object itself.
In terms of implementation, the cache nests everything under a WeakKeyDictionary whose keys are the first argument to the decorated function, namely self.
I don’t think anything additional is needed for this (?). I’d be enthusiastic about contributing a PR, if interested core devs feel supportive.
I found a few old threads on this topic, but none which directly suggested this solution.
If people want to see it trialed on pypi first, I’m happy to do it, but I find it somewhat doubtful – because the gain is so small vs adding your own caches – that many people would want to pull in a library just for this.