How to Cache Function Results in Redis with Python

A Python decorator that caches function results in Redis using TTL, with optional fakeredis for testing without a server.

Medium Python 3.9+ Aug 9, 2026 Caching & Redis 14 views 0 copies

Requires third-party packages — install first
pip install redis

Python code

45 lines
Python 3.9+
import redis
import json
import time
try:
    import fakeredis
except ImportError:
    fakeredis = None

from functools import wraps


def cache_redis(cache_key_prefix="cache", ttl=60):
    """Decorator to cache function results in Redis."""
    if fakeredis:
        r = fakeredis.FakeStrictRedis()
    else:
        r = redis.Redis(host="localhost", port=6379, db=0)

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = f"{cache_key_prefix}:{func.__name__}:{args}:{kwargs}"
            cached = r.get(key)
            if cached:
                print(f"Cache hit for {key}")
                return json.loads(cached)
            result = func(*args, **kwargs)
            r.setex(key, ttl, json.dumps(result))
            print(f"Cache miss, stored for {key}")
            return result
        return wrapper
    return decorator


@cache_redis()
def get_user_data(user_id):
    """Fetch user data (simulated slow operation)."""
    time.sleep(1)  # Simulate expensive database query
    return {"id": user_id, "name": f"User {user_id}", "active": True}


if __name__ == "__main__":
    print("First call:", get_user_data(101))
    print("Second call:", get_user_data(101))
    print("Different ID:", get_user_data(102))

Output

stdout
Cache miss, stored for cache:get_user_data:(101,):{}
First call: {'id': 101, 'name': 'User 101', 'active': True}
Cache hit for cache:get_user_data:(101,):{}
Second call: {'id': 101, 'name': 'User 101', 'active': True}
Cache miss, stored for cache:get_user_data:(102,):{}
Different ID: {'id': 102, 'name': 'User 102', 'active': True}

How it works

The decorator cache_redis wraps each function and builds a unique cache key from the function name, arguments, and kwargs. On the first call, it computes the result and stores it with setex (set with expiration). Subsequent calls with the same arguments return the cached JSON-parsed value instead of re-executing the function. The @wraps decorator preserves the original function's metadata, and fakeredis allows testing without a real Redis server. Setting a TTL prevents stale data from persisting forever.

Common mistakes

  • Not installing the `redis` package via `pip install redis` before importing.
  • Using `r.set()` without TTL, causing cached data to never expire.
  • Forgetting to use `json.dumps`/`json.loads` for storing dict results.
  • Building a key without including arguments, causing collisions between different calls.

Variations

  1. Use `pickle` instead of JSON to cache complex Python objects, though it's not human-readable.
  2. Cache with an explicit TTL override per function by adding a parameter to the decorator.

Real-world use cases

  • Caching database query results to reduce load on the database in high-traffic web apps.
  • Storing API responses from slow external services to improve response times in microservices.
  • Caching computed recommendations or pricing in e-commerce to avoid recalculating for every request.

Sponsored

Run locally

This sample needs third-party packages, so it cannot run in the browser IDE. Copy the code above, install the packages shown at the top, then run it in your own Python environment.

More from Caching & Redis

Related tutorials and quizzes for this topic.