cachetools

PythonCache LibraryMemoizationDecoratorsLRUTTLPerformance

GitHub Overview

tkem/cachetools

Extensible memoizing collections and decorators

Stars2,616
Watchers24
Forks177
Created:March 22, 2014
Language:Python
License:MIT License

Topics

None

Star History

tkem/cachetools Star History
Data as of: 10/22/2025, 10:04 AM

Cache Library

cachetools

Overview

cachetools is a Python library that provides extensible memoizing collections and decorators.

Details

cachetools is a library that extends Python's standard library functools.lru_cache to provide more diverse caching strategies. Developed by Thomas Kemmer in 2015, it offers a collection of memoizing collections and decorators. The library implements various cache algorithms including LRU (Least Recently Used), FIFO (First In First Out), TTL (Time To Live), LFU (Least Frequently Used), and TLRU, allowing you to choose the optimal caching strategy for your use case. The @cached decorator makes it easy to add caching functionality to existing functions, while @cachedmethod enables caching for class methods. All cache classes derive from collections.MutableMapping, providing dictionary-like usage with maxsize and currsize properties for cache size management. The library supports custom key functions through hashkey and methodkey, and can be made thread-safe when combined with lock objects.

Pros and Cons

Pros

  • Diverse Cache Algorithms: Rich selection including LRU, FIFO, TTL, LFU, TLRU
  • Decorator Support: Easy caching with @cached and @cachedmethod decorators
  • Standard Library Compatibility: Compatible with functools.lru_cache
  • Custom Key Functions: hashkey and methodkey for custom key generation
  • Thread Safety: Safe concurrent processing when combined with lock objects
  • Statistics: cache_info() for monitoring hit rates and miss counts
  • Memory Efficiency: Automatic expiration and size limits for memory management

Cons

  • Thread Safety: Not thread-safe by default
  • Memory Usage: High memory consumption when caching large amounts of data
  • Configuration Complexity: Need to select optimal strategy from multiple cache options
  • TTL Precision: Strict time control can be challenging with TTL caches
  • Dependencies: Cost of introducing an external library

Key Links

Usage Examples

Basic Caching

from cachetools import cached, LRUCache, TTLCache

# Basic memoization using dictionary
@cached(cache={})
def fibonacci(n):
    """Cache Fibonacci sequence calculation"""
    return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)

# Usage example
print(fibonacci(100))  # Takes time on first calculation
print(fibonacci(100))  # Fast retrieval from cache

LRU Cache

import urllib.request
from cachetools import cached, LRUCache

# Cache API responses with LRU cache
@cached(cache=LRUCache(maxsize=32))
def get_pep_document(num):
    """Fetch Python Enhancement Proposal"""
    url = f'http://www.python.org/dev/peps/pep-{num:04d}/'
    with urllib.request.urlopen(url) as response:
        return response.read()

# Usage example
pep1 = get_pep_document(1)
pep8 = get_pep_document(8)

TTL Cache (Time-based Expiration)

from cachetools import cached, TTLCache
import requests

# API call cached for 10 minutes
@cached(cache=TTLCache(maxsize=1024, ttl=600))
def get_weather_data(city):
    """Cache weather data with expiration"""
    url = f'http://api.openweathermap.org/data/2.5/weather?q={city}'
    response = requests.get(url)
    return response.json()

# Usage example
weather = get_weather_data("Tokyo")

Cache Statistics

from cachetools import cached, LRUCache

@cached(cache=LRUCache(maxsize=32), info=True)
def expensive_function(x):
    """Simulate expensive computation"""
    import time
    time.sleep(0.1)
    return x * x

# Check statistics
for i in [1, 2, 3, 1, 2, 1]:
    result = expensive_function(i)

print(expensive_function.cache_info())
# CacheInfo(hits=3, misses=3, maxsize=32, currsize=3)

Class Method Caching

from cachetools import cachedmethod, LRUCache
from cachetools.keys import hashkey
import requests

class APIClient:
    def __init__(self, cache_size=100):
        self.cache = LRUCache(maxsize=cache_size)
    
    @cachedmethod(lambda self: self.cache)
    def fetch_user_data(self, user_id):
        """Fetch user data with caching"""
        response = requests.get(f'/api/users/{user_id}')
        return response.json()
    
    @cachedmethod(lambda self: self.cache)
    def fetch_post_data(self, post_id):
        """Fetch post data with caching"""
        response = requests.get(f'/api/posts/{post_id}')
        return response.json()

# Usage example
client = APIClient()
user = client.fetch_user_data(123)
posts = client.fetch_post_data(456)

Custom Key Function

from cachetools import cached, LRUCache
from cachetools.keys import hashkey

def custom_key(*args, config={}, **kwargs):
    """Custom key generation including dictionaries"""
    key = hashkey(*args, **kwargs)
    # Convert dictionary to sorted tuple
    key += tuple(sorted(config.items()))
    return key

@cached(LRUCache(maxsize=128), key=custom_key)
def process_data(data, config={}):
    """Data processing with configuration"""
    return f"Processed {data} with {config}"

# Usage example
result1 = process_data("data1", config={"format": "json", "sort": True})
result2 = process_data("data1", config={"sort": True, "format": "json"})  # Same key

Shared Cache Across Functions

from cachetools import cached
from cachetools.keys import hashkey
from functools import partial

# Shared cache
shared_cache = {}

@cached(shared_cache, key=partial(hashkey, 'fibonacci'))
def fibonacci(n):
    return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)

@cached(shared_cache, key=partial(hashkey, 'lucas'))
def lucas_numbers(n):
    return 2 - n if n < 2 else lucas_numbers(n - 1) + lucas_numbers(n - 2)

# Both functions use the same cache
fib_result = fibonacci(10)
lucas_result = lucas_numbers(10)
print(f"Shared cache size: {len(shared_cache)}")