Python Caching — Speed Up Your Applications

Python AdvancedCachingFree Lesson

Advertisement

Python Caching — Speed Up Your Applications

Caching stores results of expensive operations to avoid recomputation. A cache hit is 1000x faster than recomputing.

Learning Objectives

  • Use functools.lru_cache for memoization
  • Implement in-memory caching with TTL
  • Integrate Redis for distributed caching
  • Apply cache invalidation strategies

Why Cache?

Without Cache:
Request → Database Query (100ms) → Response
Request → Database Query (100ms) → Response
Request → Database Query (100ms) → Response
Total: 300ms

With Cache:
Request → Cache Hit (1ms) → Response
Request → Cache Hit (1ms) → Response
Request → Cache Miss → Database (100ms) → Cache Store → Response
Total: 103ms (3x faster!)

functools.lru_cache

The easiest way to add caching — just one decorator.

from functools import lru_cache

@lru_cache(maxsize=128)  # Cache up to 128 results
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# First call: computes recursively (slow)
print(fibonacci(100))  # Fast!

# Subsequent calls: returns cached result (instant)
print(fibonacci.cache_info())
# CacheInfo(hits=98, misses=101, maxsize=128, currsize=101)

# Clear cache
fibonacci.cache_clear()

TTL Cache (Time-To-Live)

from functools import lru_cache
from datetime import timedelta
from cachetools import TTLCache

# cachetools provides TTL support
cache = TTLCache(maxsize=100, ttl=300)  # 5 minute TTL

def get_user(user_id):
    if user_id in cache:
        return cache[user_id]

    # Expensive database query
    user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
    cache[user_id] = user
    return user

In-Memory Cache with TTL

import time
from typing import Any, Optional

class SimpleCache:
    def __init__(self, default_ttl: int = 300):
        self.cache = {}
        self.default_ttl = default_ttl

    def get(self, key: str) -> Optional[Any]:
        if key in self.cache:
            value, timestamp = self.cache[key]
            if time.time() - timestamp < self.default_ttl:
                return value
            del self.cache[key]  # Expired
        return None

    def set(self, key: str, value: Any, ttl: int = None):
        self.cache[key] = (value, time.time())

    def delete(self, key: str):
        self.cache.pop(key, None)

    def clear(self):
        self.cache.clear()

Redis Caching

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def cache_result(key: str, func, ttl: int = 300):
    """Cache a function result in Redis."""
    cached = r.get(key)
    if cached:
        return json.loads(cached)

    result = func()
    r.setex(key, ttl, json.dumps(result))
    return result

# Usage
user_data = cache_result(
    "user:123",
    lambda: fetch_user_from_db(123),
    ttl=600
)

Cache Invalidation Strategies

# 1. Time-based (TTL) — simplest
# Data expires after N seconds

# 2. Event-based — invalidate on change
def update_user(user_id, data):
    db.update(user_id, data)
    r.delete(f"user:{user_id}")  # Invalidate cache

# 3. Pattern-based — invalidate groups
def invalidate_pattern(pattern):
    keys = r.keys(pattern)
    if keys:
        r.delete(*keys)

invalidate_pattern("user:*")  # Clear all user caches

Key Takeaways

  1. lru_cache is the easiest way to add caching
  2. Use TTL (time-to-live) for cache expiration
  3. Redis enables distributed caching across servers
  4. Cache invalidation is hard — choose strategies carefully
  5. Cache expensive computations, not simple lookups
  6. Monitor cache hit ratio (aim for greater than 80%)
  7. Use @lru_cache for recursive functions
  8. Consider cache warming for critical data

Advertisement

Need Expert Python Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement