Skip to main content

Detect memory-leak patterns in Python's functools.lru_cache usage.

Project description

lrucheck

A small static checker that finds memory leaks from functools.lru_cache and functools.cache in Python code.

Why

Python's lru_cache is easy to use, but it has two common traps:

  1. @lru_cache on a method. The cache holds the self argument. Your instance never gets garbage-collected. The longer the program runs, the more memory it uses.
  2. @lru_cache(maxsize=None) or @cache. The cache has no size limit. It grows forever as new arguments come in.

lrucheck reads your code (without running it) and prints a warning when it finds these patterns.

Install

uv add lrucheck

Use

Scan a file:

lrucheck path/to/file.py

Scan a folder (recursive):

lrucheck src/

Example

Given this file service.py:

from functools import lru_cache, cache


class UserService:
    @lru_cache(maxsize=128)
    def find_user(self, user_id):
        return load_user(user_id)

    @cache
    def get_settings(self, user_id):
        return load_settings(user_id)


@lru_cache
def heavy_compute(value):
    return value * value

Run lrucheck:

$ lrucheck service.py
service.py:5:6: LRU001 `@lru_cache` on a method keeps `self` in the cache and leaks the instance
service.py:9:6: LRU001 `@lru_cache` on a method keeps `self` in the cache and leaks the instance
service.py:9:6: LRU002 `@lru_cache(maxsize=None)` or `@cache` has no size limit and can grow forever
service.py:14:2: LRU002 `@lru_cache(maxsize=None)` or `@cache` has no size limit and can grow forever
$ echo $?
1

The output format is the same as flake8 and ruff, so editors and CI tools can read it.

Rules

Code What it finds
LRU001 @lru_cache or @cache on a method. The cache keeps self, so the instance is never freed.
LRU002 @lru_cache(maxsize=None) or @cache. The cache has no size limit and can grow forever.

Bad

from functools import lru_cache

class Service:
    @lru_cache(maxsize=None)   # LRU001 + LRU002
    def fetch(self, key):
        return load(key)

Good

from functools import lru_cache

@lru_cache(maxsize=128)
def fetch(key):
    return load(key)

If you must keep the cache near a class, use a @staticmethod or a top-level function:

class Service:
    @staticmethod
    @lru_cache(maxsize=128)
    def fetch(key):
        return load(key)

Exit codes

Code Meaning
0 No problems found.
1 One or more rule errors found.
2 A path was missing, a file could not be read, or a file had a syntax error.

This makes lrucheck easy to use in CI: a non-zero exit fails the build.

Pre-commit (planned)

A pre-commit hook is on the roadmap (see TODO.md). For now, you can run lrucheck from a script or a Makefile.

Roadmap

  • LRU003@lru_cache defined inside a function or closure (a new cache per call)
  • LRU004 — wrong decorator order with @staticmethod or @classmethod
  • Read config from pyproject.toml [tool.lrucheck]
  • # noqa: LRU001 to skip a single line
  • --select and --ignore to turn rules on or off
  • JSON output for editors

See TODO.md for the full list.

Development

This project uses uv.

uv sync
uv run pytest
uv run lrucheck tests/examples/

Pre-commit

The project uses pre-commit to run checks before each commit:

  • ruff for linting and formatting
  • codespell for spelling
  • ty for type checking
  • standard hooks for trailing whitespace, end of file, large files

To set it up once:

uv run pre-commit install

After this, the hooks run on every git commit. You can also run them on all files at any time:

uv run pre-commit run --all-files

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lrucheck-0.1.0.tar.gz (7.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lrucheck-0.1.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file lrucheck-0.1.0.tar.gz.

File metadata

  • Download URL: lrucheck-0.1.0.tar.gz
  • Upload date:
  • Size: 7.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for lrucheck-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ad260e3b92acc2764beb3adc3341d3d20e9aeabd96858bbe4d1033fb31c1d104
MD5 af7bfc69b29f52f726167224adcf7f56
BLAKE2b-256 dac2e5b04aa2a28bfa648b9cf9f25b14e5a727a5d9034ddafb3139a3fa9e30b0

See more details on using hashes here.

File details

Details for the file lrucheck-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lrucheck-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for lrucheck-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4d6087d6cc65996fb4b20528ad3e4ccbca2c643db6d91442e126f4c7ce8e7af0
MD5 f58d5045a9836139e97c59ba1cc4a5d7
BLAKE2b-256 c275f9a5b94829392be3b2bf9d2523b9c86edfd8852066b0a1a5987d67070fb3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page