Per-instance LRU caching for methods — a drop-in replacement for functools.lru_cache that avoids memory leaks.
Project description
lru-method-cache
A drop-in replacement for functools.lru_cache designed for methods.
The problem
Using functools.lru_cache on methods causes memory leaks. The cache is
stored on the class (via the decorator), and every cached call stores a
strong reference to self as part of the cache key. This prevents
instances from being garbage collected, even after all other references
are gone:
from functools import lru_cache
class MyClass:
@lru_cache(maxsize=128)
def expensive(self, x):
return x * 2
a = MyClass()
a.expensive(1)
del a # instance is NOT garbage collected — the cache still holds a reference
This is a well-known issue with no satisfying solution in the standard library. Here are some references to learn more.
- Python Docs: https://docs.python.org/3/faq/programming.html#how-do-i-cache-method-calls
- Blog post: https://rednafi.com/python/lru_cache_on_methods/
- Stack Overflow: https://stackoverflow.com/q/33672412
The solution
lru-method-cache provides lru_method_cache, which stores the cache
per instance using a descriptor. When an instance is garbage
collected, its cache is automatically cleaned up via weakref.finalize.
from lru_method_cache import lru_method_cache
class MyClass:
@lru_method_cache(maxsize=128)
def expensive(self, x):
return x * 2
a = MyClass()
a.expensive(1)
del a # instance is garbage collected, the respective cache is freed
Signature normalization
Unlike functools.lru_cache, lru_method_cache normalizes call
signatures so that positional and keyword forms of the same argument
produce the same cache key:
class MyClass:
@lru_method_cache
def compute(self, a, b, c=0):
return a + b + c
obj = MyClass()
obj.compute(1, 2) # miss
obj.compute(1, b=2) # hit — same as above
obj.compute(a=1, b=2) # hit — same as above
functools.lru_cache treats these as three separate cache entries.
Usage
from lru_method_cache import lru_method_cache
class MyClass:
# Without parentheses (defaults: max_size=128, typed=False)
@lru_method_cache
def method_a(self, x):
...
# With parentheses
@lru_method_cache(max_size=256, typed=True)
def method_b(self, x):
...
# Unlimited cache
@lru_method_cache(max_size=None)
def method_c(self, x):
...
The familiar cache_info() and cache_clear() methods are available on
each bound method, just like functools.lru_cache:
obj = MyClass()
obj.method_a(1)
obj.method_a(2)
obj.method_a(1) # cache hit
obj.method_a.cache_info()
# CacheInfo(hits=1, misses=2, max_size=128, cur_size=2)
obj.method_a.cache_clear()
Parameters
| Parameter | Default | Description |
|---|---|---|
max_size |
128 |
Maximum number of cached results. None for unlimited. |
typed |
False |
If True, arguments of different types are cached separately (e.g., 1 and 1.0 are distinct). |
Classmethods and staticmethods
lru_method_cache is only for instance methods. Applying it to a
classmethod or staticmethod raises TypeError with a helpful
message — those don't have the memory leak problem, so
functools.lru_cache works fine for them.
Classes with __slots__
If a class uses __slots__ without __weakref__, the cache still works
but a UserWarning is issued because cleanup cannot happen automatically
when the instance is deleted. Add "__weakref__" to __slots__ to
enable automatic cleanup.
How it compares
There are certain other clever solutions for the issue of using lru_cache on class methods. Here is a comparison of this project to some of other solutions:
| Approach | Memory-safe | Signature normalization | Per-instance cache | Thread-safe |
|---|---|---|---|---|
functools.lru_cache |
No | No | No | Yes |
methodtools.lru_cache |
Yes | No | Yes | Yes |
cachetools + manual wiring |
Yes | No | Manual | Manual |
lru-method-cache.lru_method_cache |
Yes | Yes | Yes | Yes |
Design philosophy
lru-method-cache uses only Python's standard library — no third-party
dependencies. The implementation is a single, short module that is easy
to read, inspect, and audit. If you want to understand exactly what your
caching decorator does, you can review the entire source in a few
moments.
Installation
pip install lru-method-cache
Requirements
Python 3.10+. No third-party dependencies.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lru_method_cache-0.2.0.tar.gz.
File metadata
- Download URL: lru_method_cache-0.2.0.tar.gz
- Upload date:
- Size: 9.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e777a8d2dc363b121ce8c5e34dee063a28f37eabe56c35e6dd9b3a52b41000cc
|
|
| MD5 |
e790377e4b573d535a4dbbc530b4c39c
|
|
| BLAKE2b-256 |
3679ca29614d46469cde804e723769e2c96f682a1ce5d3a5a625038bf7192f05
|
Provenance
The following attestation bundles were made for lru_method_cache-0.2.0.tar.gz:
Publisher:
publish.yml on mkoistinen/lru-method-cache
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lru_method_cache-0.2.0.tar.gz -
Subject digest:
e777a8d2dc363b121ce8c5e34dee063a28f37eabe56c35e6dd9b3a52b41000cc - Sigstore transparency entry: 1070359353
- Sigstore integration time:
-
Permalink:
mkoistinen/lru-method-cache@6cd202d1075f0172c9afed978e30ac184a8373b8 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mkoistinen
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6cd202d1075f0172c9afed978e30ac184a8373b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file lru_method_cache-0.2.0-py3-none-any.whl.
File metadata
- Download URL: lru_method_cache-0.2.0-py3-none-any.whl
- Upload date:
- Size: 7.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87f23d96ae1f00d3fe2dda399565d7e7e94fc29d253cb3d356574d3ada5d7ca8
|
|
| MD5 |
b29f2055771fc20d25908cb18eda2c44
|
|
| BLAKE2b-256 |
a58529a4de6faeebba1d491d874d3fdb6f8d80255b9f8436c5533e643b3dac52
|
Provenance
The following attestation bundles were made for lru_method_cache-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on mkoistinen/lru-method-cache
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lru_method_cache-0.2.0-py3-none-any.whl -
Subject digest:
87f23d96ae1f00d3fe2dda399565d7e7e94fc29d253cb3d356574d3ada5d7ca8 - Sigstore transparency entry: 1070359397
- Sigstore integration time:
-
Permalink:
mkoistinen/lru-method-cache@6cd202d1075f0172c9afed978e30ac184a8373b8 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mkoistinen
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6cd202d1075f0172c9afed978e30ac184a8373b8 -
Trigger Event:
release
-
Statement type: