DEV Community

Alex Chen
Alex Chen

Posted on

Quick Tip: Cache Function Results with lru_cache — Even When Args Are Dicts

Quick Tip

functools.lru_cache is the fastest memoization you'll ever add — one decorator:

from functools import lru_cache

@lru_cache(maxsize=1024)
def fetch_user(user_id: int) -> dict:
    return requests.get(f"https://api.example.com/users/{user_id}").json()
Enter fullscreen mode Exit fullscreen mode

But it blows up the moment an argument is unhashable:

@lru_cache(maxsize=128)
def query(sql: str, params: dict) -> list: ...
# TypeError: unhashable type: 'dict'
Enter fullscreen mode Exit fullscreen mode

The fix is a frozen-key wrapper — 4 lines:

def frozen(d):
    return tuple(sorted((k, frozen(v) if isinstance(v, dict) else v) for k, v in d.items()))

@lru_cache(maxsize=128)
def _query(sql, frozen_params): ...

def query(sql, params):
    return _query(sql, frozen(params))
Enter fullscreen mode Exit fullscreen mode

On a reporting script I run daily, this took 40 redundant DB round-trips down to 6 unique ones — 73s → 11s wall clock. Caveat: maxsize=None on a long-lived process is a memory leak in disguise; always set a bound.

I draft utilities like this with MonkeyCode's free tier: https://ly.cyberserval.tech/iIETXiF

What's the dumbest place you've found an lru_cache that was silently eating RAM?

Top comments (0)