# Adding a method caching decorator to functools

**URL:** <https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164>\
**Category:** Ideas\
**Created:** [May 3, 2026, 4:51am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164 "2026-05-03T04:51:24Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![sirosen](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sirosen/32/8478_2.png) [@sirosen](https://discuss.python.org/u/sirosen)\
**Post date:** [May 3, 2026, 4:51am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/1 "2026-05-03T04:51:24Z")

</div>

## 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:

```python
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.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [May 3, 2026, 7:00am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/2 "2026-05-03T07:00:13Z")

</div>

This sounds like a reasonable addition to `functools`.

---

<div class="post-metadata">

**Author:** ![duncathan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/duncathan/32/25521_2.png) [@duncathan](https://discuss.python.org/u/duncathan)\
**Post date:** [May 3, 2026, 7:01am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/3 "2026-05-03T07:01:58Z")

</div>

In my experience, the major annoyance when it comes to trying to cache methods is that `self` needs to be hashable. It doesn’t seem that this proposal addresses that pain point - I would personally be more interested in something standardized that stores the cache as a property on the object, for these cases. That said, this does seem a reasonable solution for the problem presented, I’ve just never encountered that problem in particular while I come up against the hashability requirement with some frequency

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 3, 2026, 7:36am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/4 "2026-05-03T07:36:57Z")

</div>

TBH I’d be tempted to do this with the cache stored on the instance itself, rather than as a weak dict. It’s easier to reason about the lifetime of objects that way; the rule is simply “when the object expires, its cache disappears”. Probably wouldn’t play well with ` __slots__ ` though.

---

<div class="post-metadata">

**Author:** ![xitop](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/xitop/32/13242_2.png) [@xitop](https://discuss.python.org/u/xitop)\
**Post date:** [May 3, 2026, 8:45am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/5 "2026-05-03T08:45:37Z")

</div>

I’d like to propose that the name of the decorator and perhaps the title of the whole discussion should indicate that the main topic is per-instance caching. We don’t want to suggest that `@method_cache` for methods is what `@cache` is for functions.

It is more complicated that that. From a little bit broader perspective there are two `@cache` issues affecting the `self` argument: the discussed instance lifetime (and GC at its end) and the missing ability to exclude an argument from caching - and that argument is often `self`, but can be also something else. Many (but not all) problems can be solved by factoring out a core function and applying a cache decorator to that one, so its partially a documentation issue (mini-HOWTO?) as well.

---

<div class="post-metadata">

**Author:** ![sirosen](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sirosen/32/8478_2.png) [@sirosen](https://discuss.python.org/u/sirosen)\
**Post date:** [May 3, 2026, 2:50pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/6 "2026-05-03T14:50:08Z")

</div>

> [@Rosuav](#):
>
> TBH I’d be tempted to do this with the cache stored on the instance itself, rather than as a weak dict. It’s easier to reason about the lifetime of objects that way; the rule is simply “when the object expires, its cache disappears”. Probably wouldn’t play well with ` __slots__ ` though.

Yeah, I have a similar instinct and see the same problem case – if you’re trying to eke out all of the performance you can get, you’re possibly using slotted classes.

This also relates to supporting unhashable types, as mentioned above. If the cache were stored on the instance, then `self` doesn’t need to be hashable.

But I also don’t know what attribute name we’d use for this. So although there’s an alternative implementation possible here, it’s not as concrete as the weakref dict.

Personally, I’d like to choose the path I can see through to completion, and leave the other way of doing this as “roll your own”.

> [@xitop](#):
>
> I’d like to propose that the name of the decorator and perhaps the title of the whole discussion should indicate that the main topic is per-instance caching. We don’t want to suggest that `@method_cache` for methods is what `@cache` is for functions.

I _do_ want to suggest, at least to a new developer, that `method_cache` is to methods as `cache` is to bare functions.

In fact, in the implementation I propose `self` is not really excluded from the cache key. It would be possible, with the weakref dict, to have two instances of a class compare equal and therefore share a cache. I don’t see this as a defect; it’s just a natural consequence of the approach.

You’re right that sometimes methods don’t make use of `self` – you could turn them into static methods or move them off of your classes to make caching work today. But that’s not material to caching on methods which _do_ access the local instance.

I’m open to being swayed to approach this all differently, but I don’t think I understand what you’re suggesting. What should this be called instead of `method_cache`?

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 3, 2026, 3:10pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/7 "2026-05-03T15:10:38Z")

</div>

> [@sirosen](#):
>
> Personally, I’d like to choose the path I can see through to completion, and leave the other way of doing this as “roll your own”.

That’s very fair. Every choice has consequences, and it’s better to pick something and run with it than to juggle all of them.

As a means of avoiding the “unhashable self” problem, would it be better to use `id(self)` as the key, and maintain a separate collection of weak refs? But, again, do what you think is right; I’m curious about all the options but don’t take that as criticism.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [May 3, 2026, 3:53pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/8 "2026-05-03T15:53:42Z")

</div>

> [@Rosuav](#):
>
> It’s easier to reason about the lifetime of objects that way; the rule is simply “when the object expires, its cache disappears”.

Is that not also true with a cache that’s a weak-keyed dictionary keyed on `self` as well? When the object expires, the entry in the dictionary for that object is removed, i.e., its cache disappears. Or do you mean something different here?

Also, I don’t think unhashable classes should be a showstopper here - by default class instances hash to their `id`, so for the common case, things would just work. And `functools.cache` has the same issue with unhashable classes, so I don’t think we should _necessarily_ expect more from this new decorator.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 3, 2026, 3:56pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/9 "2026-05-03T15:56:58Z")

</div>

> [@pf\_moore](#):
>
> > [@Rosuav](#):
> >
> > It’s easier to reason about the lifetime of objects that way; the rule is simply “when the object expires, its cache disappears”.
> 
> Is that not also true with a cache that’s a weak-keyed dictionary keyed on `self` as well? When the object expires, the entry in the dictionary for that object is removed, i.e., its cache disappears. Or do you mean something different here?

Hmm, does the weakref get disposed of promptly as soon as there are no strong refs, or does it wait around until the GC does a pass? Perhaps I’m misunderstanding. Of course, there’s no inherent guarantee that an object will be disposed of promptly even without that, but it is certainly the normal thing.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [May 3, 2026, 5:53pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/10 "2026-05-03T17:53:42Z")

</div>

I believe that method caching _should_ usually include the instance, therefore different instances have their own caches, and it makes attaching the cache to instances seem the more appealing option. This is _generally_ safer as usually different instances of the same class _shouldn’t_ have the same result for the same non-self inputs. (and in cases where this isn’t desired, use a function, not a method)

It’s possible to make this “opt-in compatible with slots” if the name of the attribute where the decorators will put the cache is deterministic and documented or user provided. Users wanting to use this with slotted classes need only include that name in slots.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [May 3, 2026, 7:47pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/11 "2026-05-03T19:47:20Z")

</div>

> [@pf\_moore](#):
>
> > [@Rosuav](#):
> >
> > It’s easier to reason about the lifetime of objects that way; the rule is simply “when the object expires, its cache disappears”.
> 
> Is that not also true with a cache that’s a weak-keyed dictionary keyed on `self` as well? When the object expires, the entry in the dictionary for that object is removed, i.e., its cache disappears. Or do you mean something different here?

this interacts with slots similarly with what I would suggest, as slotted classes will need to explicitly enable weakref support. (by adding ` __weakref__ ` to slots)

Either way here seems fine to me as long as the method the decorator will use is documented so that users _can_ compose with slotted classes.

Weakref finalization is relatively unoticable on performance in the absence of reference cycles, but increases the gc pause time in a noticable way if a large number of objects involved in a cycle requiring gc to collect also have weakrefs to finalize. I don’t think this is a reason to decide against this option on it’s own, but it may be something that leads to some people still DIYing caching if impacted.

---

<div class="post-metadata">

**Author:** ![csm10495](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/csm10495/32/9731_2.png) [@csm10495](https://discuss.python.org/u/csm10495)\
**Post date:** [May 3, 2026, 11:21pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/12 "2026-05-03T23:21:46Z")

</div>

Is there a way to make the existing cache method work for methods as well? I’d prefer changing the behavior to work where expected instead of a new method.

---

<div class="post-metadata">

**Author:** ![dr\_carlos](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dr_carlos/32/30461_2.png) [@dr\_carlos](https://discuss.python.org/u/dr_carlos)\
**Post date:** [May 3, 2026, 11:28pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/13 "2026-05-03T23:28:19Z")

</div>

Yes, certainly. But it would totally break backwards compatibility.

---

<div class="post-metadata">

**Author:** ![sirosen](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sirosen/32/8478_2.png) [@sirosen](https://discuss.python.org/u/sirosen)\
**Post date:** [May 4, 2026, 2:13am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/14 "2026-05-04T02:13:51Z")

</div>

> [@csm10495](#):
>
> Is there a way to make the existing cache method work for methods as well?

I tend to say “no”, until proven otherwise.

It _does_ work! Which is almost precisely the problem. The implications of having a process-global cache whose keys include objects intended to have limited lifetimes are very clear… Once you know to look.

To avoid putting objects into a persistent global cache, the cache has to have specialized behavior. So you need a way to decide to use that different behavior. If there’s a way of making that decision _other than_ having the caller specify (e.g., by function name), then I’m happy to discuss a concrete counterproposal. But it has to be specific, not vaguely “somehow”.

For my part, I can only think of ways which are fragile and incur significant overhead.

> [@mikeshardmind](#):
>
> It’s possible to make this “opt-in compatible with slots” if the name of the attribute where the decorators will put the cache is deterministic and documented or user provided. Users wanting to use this with slotted classes need only include that name in slots.

FWIW, I’d tend towards it being documented as `_{method name}_cache` if we go down this route, and make it possible to override.

But I think the whole path here ends up worse for users. For example, once the cache is an attribute of the instances, it has to be handled WRT pickle. I don’t have any particular opinion about what resolution would be there – probably “do nothing” and allow people to decide whether or not their caches should be pickled? – but it highlights that adding attributes increases the surface area.

The point in favor of putting caches on instances is that they don’t share results. I’m thinking about it and can’t imagine scenarios in which you want to define hashing and you want cached methods, but you don’t want instances with the same hash to share a cache. I can try to contrive one, but I’m not able to think one up which occurs naturally. It would mean you want two instances to look the same to an external cache but not to an internal one. Isn’t that very rare?

Overall, I think “caches on instances” feels a little cleaner, but produces a more complex public API. I’d rather accept some (documented!) messy edge cases in exchange for a simpler API.

It’s also possible to support both in the future, FWIW, so this is potentially just about what the default tradeoff should be.

> [@mikeshardmind](#):
>
> slotted classes will need to explicitly enable weakref support. (by adding ` __weakref__ ` to slots)

I hadn’t realized that there was this interplay! I’ve never combined weakrefs and slotted classes before.

Regarding the whole discussion of when weakrefs are cleaned up, and perf impact, I believe they are cleaned right after GC deletes an object, but I’m not certain. But the weakref docs seem to intentionally leave some wiggle room here, mentioning that some behaviors are specific to the implementation’s GC.

The perf impact would generally be negligible because the proposal isn’t to use any fancy or nontrivial finalizers.

* * *

Right now it looks like sentiment is neutral-to-positive. I’ll try to make some time in the next couple of weeks to work up a draft PR. There’s plenty of time for more feedback, of course.

---

<div class="post-metadata">

**Author:** ![BenjyWiener](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/benjywiener/32/33309_2.png) [@BenjyWiener](https://discuss.python.org/u/BenjyWiener)\
**Post date:** [May 4, 2026, 6:47am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/15 "2026-05-04T06:47:23Z")

</div>

> [@csm10495](#):
>
> Is there a way to make the existing cache method work for methods as well? I’d prefer changing the behavior to work where expected instead of a new method.

> [@sirosen](#):
>
> I tend to say “no”, until proven otherwise.
> 
> It _does_ work! Which is almost precisely the problem. The implications of having a process-global cache whose keys include objects intended to have limited lifetimes are very clear… Once you know to look.
> 
> To avoid putting objects into a persistent global cache, the cache has to have specialized behavior. So you need a way to decide to use that different behavior. If there’s a way of making that decision _other than_ having the caller specify (e.g., by function name), then I’m happy to discuss a concrete counterproposal. But it has to be specific, not vaguely “somehow”.

What if `functool.[lru_]cache` gained a `weak: Container[int | str]` keyword argument? Arguments at the given indices/names will be cached via weakref. This way it can be used for caching other functions where strong references are undesirable.

Linters can warn about caching methods without specifying `weak=[0]` (or `weak=[]` to use the current behavior).

```python
class MyClass:
    @cache(weak=[0])
    def my_method(self, x: int) -> bool:
        ...

```

Needing to specify index and name separately can be confusing, but `lru_cache` already caches positional and keyword arguments separately, and the introspection required to match them up automatically is relatively expensive and not always possible.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 4, 2026, 6:54am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/16 "2026-05-04T06:54:15Z")

</div>

> [@BenjyWiener](#):
>
> What if `functool.[lru_]cache` gained a `weak: Container[int | str]` keyword argument? Arguments at the given indices/names will be cached via weakref. This way it can be used for caching other functions where strong references are undesirable.

YAGNI. I don’t think that level of flexibility is necessary. (If you actually do need it, it’s not too hard to roll your own.) Done this way, there’d need to then also be a cached\_method helper function (if you’re trying to cache a method, you aren’t going to stop and think “hey, I might need to use weak refs on the first argument”, but you might very well notice that the next thing after @cache is @cached\_method), and that helper would be pretty much the only usage that the feature gets, so… let’s just make the feature that’s needed.

---

<div class="post-metadata">

**Author:** ![DavidCEllis](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidcellis/32/9789_2.png) [@DavidCEllis](https://discuss.python.org/u/DavidCEllis)\
**Post date:** [May 4, 2026, 10:24am UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/17 "2026-05-04T10:24:52Z")

</div>

Outside of any GC concerns, I think `@cached_method` should be expected to work wherever `@cached_property` would work which would require working on classes with unhashable instances. I think the cleanest way to do this would be storing the cache in a defined attribute on an instance over any attempts to try to handle unhashable weakrefs.

The main reason I would want this to work on unhashable classes is that while default classes are hashed by `id`, the default _dataclass_ is unhashable. Making the class hashable requires either making the whole class frozen or setting `unsafe_hash=True`, neither of which may be desirable.

With regard to the cache being pickled, `cached_property` already includes the cached result in the pickle, so I wouldn’t be surprised by `cached_method` having matching behaviour.

* * *

One nice thing you could potentially do is make `dataclasses` aware of `@cached_method` and have slotted dataclasses automatically create the necessary slots. Only users creating slotted classes directly would then have to deal with the cache names which I think would go a fair way to making it nicer to use.

---

<div class="post-metadata">

**Author:** ![duncathan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/duncathan/32/25521_2.png) [@duncathan](https://discuss.python.org/u/duncathan)\
**Post date:** [May 4, 2026, 1:35pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/18 "2026-05-04T13:35:55Z")

</div>

> [@DavidCEllis](#):
>
> The main reason I would want this to work on unhashable classes is that while default classes are hashed by `id`, the default _dataclass_ is unhashable. Making the class hashable requires either making the whole class frozen or setting `unsafe_hash=True`, neither of which may be desirable.

Chiming in to verify that 99% of cases where I want to cache a method on an unhashable class, it’s on a non-frozen dataclass. `unsafe_hash=True` isn’t permitted in my projects so it’s not a viable workaround for us.

---

<div class="post-metadata">

**Author:** ![Wombat](https://avatars.discourse-cdn.com/v4/letter/w/e480ec/32.png) [@Wombat](https://discuss.python.org/u/Wombat)\
**Post date:** [May 4, 2026, 2:35pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/19 "2026-05-04T14:35:35Z")

</div>

> [@sirosen](#):
>
> The bug being that `Foo` instances will never be GCed after `Foo.bar()` is called, since the cache is holding a reference to them.

I thought that `@cache` meant “cache forever” and that `@lru_cache` meant “cache until the entry ages out”. So the `@cache` example on a method is doing what it is supposed to. That is potentially useful even beyond the lifetime of the instance:

```python
class T(tuple):
    @cache
    def sorted(self):
        return sorted(self)

```

Since `T` implements ` __eq__ ` and ` __hash__ `, the cache would work for **all** instances of `T` that are equal even if one of them stops being used. In this case, the long-lived cache is helpful because it saves a redundant computation. One instance might be gone but another one that is equal to it would still benefit.

If a limited lifetime is desired, switch to `@lru_cache`, then unused objects age out naturally without need for weak referencing.

---

<div class="post-metadata">

**Author:** ![Wombat](https://avatars.discourse-cdn.com/v4/letter/w/e480ec/32.png) [@Wombat](https://discuss.python.org/u/Wombat)\
**Post date:** [May 4, 2026, 3:04pm UTC](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164/20 "2026-05-04T15:04:26Z")

</div>

> [@sirosen](#):
>
> In terms of implementation, the cache nests everything under a `WeakKeyDictionary` whose keys are the first argument to the decorated function, namely `self`.

There are some implementations in the answers to this StackOverflow question: [https://stackoverflow.com/questions/33672412](https://stackoverflow.com/questions/33672412)

First one:

```python
import functools
import weakref

def memoized_method(*lru_args, **lru_kwargs):
    def decorator(func):
        @functools.wraps(func)
        def wrapped_func(self, *args, **kwargs):
            # We're storing the wrapped method inside the instance. If we had
            # a strong reference to self the instance would never die.
            self_weak = weakref.ref(self)
            @functools.wraps(func)
            @functools.lru_cache(*lru_args, **lru_kwargs)
            def cached_method(*args, **kwargs):
                return func(self_weak(), *args, **kwargs)
            setattr(self, func. __name__ , cached_method)
            return cached_method(*args, **kwargs)
        return wrapped_func
    return decorator

```

Second one:

```python
import functools
import weakref

def weak_lru(maxsize=128, typed=False):
    'LRU Cache decorator that keeps a weak reference to "self"'
    def wrapper(func):

        @functools.lru_cache(maxsize, typed)
        def _func(_self, *args, **kwargs):
            return func(_self(), *args, **kwargs)

        @functools.wraps(func)
        def inner(self, *args, **kwargs):
            return _func(weakref.ref(self), *args, **kwargs)

        return inner

    return wrapper

```

Third one:

```python
from weakref import WeakKeyDictionary, WeakValueDictionary, ref
from functools import lru_cache, wraps

class IdKey:
    def __init__ (self, value):
        self._id = id(value)
    def __hash__ (self):
        return self._id
    def __eq__ (self, other):
        return self._id == other._id
    def __repr__ (self):
        return f"<IdKey(_id={self._id})>"

def lru_cache_method(*lru_args, **lru_kwargs):

    def decorator(method):

        instances = WeakValueDictionary()
        methods = WeakKeyDictionary()

        @wraps(method)
        def cached_method(self, *args, **kwargs):
            key = IdKey(self)
            weakly_bound_cached_method = methods.get(key)
            if weakly_bound_cached_method is None:
                # NOTE This prevents `key` from being GCed until `self` is GCed.
                instances[key] = self
                
                # NOTE This makes sure self can be GCed before `bound_cached_method` is GCed,
                # by avoiding a mutual dependency.
                _self = ref(self)

                @wraps(method)
                @lru_cache(*lru_args, **lru_kwargs)
                def weakly_bound_cached_method(*args, **kwargs):
                    return method(_self(), *args, **kwargs)
                
                # NOTE This entry can be GCed as soon as `self` is GCed.
                methods[key] = weakly_bound_cached_method

            return weakly_bound_cached_method(*args, **kwargs)
        
        return cached_method
    
    return decorator

```

Two other answers suggest that often all that is needed is to add `@staticmethod` on top of the `@cache`. If the result depends on the method arguments but not the instance, this is the easiest way to go.

There is also an implementation in the [methodtools](https://pypi.org/project/methodtools/) project on PyPI,

[Next page](https://discuss.python.org/t/adding-a-method-caching-decorator-to-functools/107164.md?page=2)
