cachetools Documentation

repository·master·Indexed 25 days ago

https://github.com/tkem/cachetools

Extensible memoizing collections and decorators for Python. Provides various cache implementations based on eviction algorithms including LRU (Least Recently Used), TTL (Time-To-Live), FIFO (First-In-First-Out), LFU (Least Frequently Used), and RRCache (Random). Includes decorators like @cached and @cachedmethod for function and instance method memoization, as well as the cachetools.func module for decorators compatible with the functools.lru_cache API.

Tokens
5.3K
Snippets
14
Records
34
Agent score
76%

What's inside cachetools

  1. Implement various cache algorithms

    master

    The cachetools module provides several cache implementations based on different eviction algorithms. All cache classes derive from Cache (which inherits from collections.abc.MutableMapping) and support maxsize and currsize properties.

    Important Notes:

    • maxsize must be a positive number. Use math.inf for an unbounded cache.
    • Cache size is determined by the sum of the sizes of its values. You can provide a custom getsizeof(value) -> int callable to the constructor to define how item size is calculated.
    • Thread Safety: Cache classes are not thread-safe. Use a lock object with memoizing decorators to synchronize access in multi-threaded environments.
  2. Implement custom key functions for non-hashable arguments

    master

    If a function accepts non-hashable arguments (like a dict), the default @cached decorator will raise a TypeError. To fix this, provide a custom key function to the decorator that extracts hashable components from the arguments.

    def envkey(*args, env={}, **kwargs):
        key = hashkey(*args, **kwargs)
        key += tuple(sorted(env.items()))
        return key
    
    @cached(LRUCache(maxsize=128), key=envkey)
    def foo(x, y, z, env={}):
        pass
  3. Handle exceptions in cached functions

    master

    By default, cachetools does not cache function calls if an exception is raised. If you want to cache exceptions (e.g., to avoid repeated failed network requests), you must wrap the logic in a helper function that catches the exception and returns it as a regular value, then cache that helper.

    @cached(cache=LRUCache(maxsize=10), info=True)
    def _get_pep_wrapped(num):
        try:
            # ... perform operation ...
            return result
        except Exception as e:
            return e  # Return exception as a value to be cached
    
    def get_pep(num):
        res = _get_pep_wrapped(num)
        if isinstance(res, Exception):
            raise res
        return res
  4. Memoize functions using the @cached decorator

    master

    The @cached decorator allows you to easily memoize function and method calls by providing a cache object. You can use standard dictionaries for simple caching, or specialized cache classes like LRUCache and TTLCache for more advanced eviction policies.

    from cachetools import cached, LRUCache, TTLCache
    
    # Simple caching with a dictionary
    @cached(cache={})
    def fib(n):
        return n if n < 2 else fib(n - 1) + fib(n - 2)
    
    # Least Recently Used (LRU) caching
    @cached(cache=LRUCache(maxsize=32))
    def get_pep(num):
        # ... implementation ...
        pass
    
    # Time-To-Live (TTL) caching
    @cached(cache=TTLCache(maxsize=1024, ttl=600))
    def get_weather(place):
        # ... implementation ...
        pass
  5. Extend cache classes by overriding popitem or expire

    master

    To monitor or react to cache evictions, you can subclass cache implementations:

    1. To track evictions (LRU, FIFO, etc.): Override popitem(). This is called when the cache is full and needs to make space.
    2. To track expirations (TTLCache, TLRUCache): Override expire(time=None). This is called during mutating operations to clean up expired items.
    # Tracking evictions
    class MyCache(LRUCache):
        def popitem(self):
            key, value = super().popitem()
            print(f'Key "{key}" evicted with value "{value}"')
            return key, value
    
    # Tracking expirations
    class ExpCache(TTLCache):
        def expire(self, time=None):
            items = super().expire(time)
            print(f"Expired items: {items}")
            return items
  6. Use Cache subclasses for different eviction policies

    master

    The cachetools library provides several specialized cache implementations based on different eviction policies. All subclasses inherit from Cache and behave like a MutableMapping.

    Available cache types:

    • FIFOCache: First In First Out. Removes the oldest items first.
    • LFUCache: Least Frequently Used. Removes items with the lowest access count.
    • LRUCache: Least Recently Used. Removes items that haven't been accessed for the longest time.
    • RRCache: Random Replacement. Removes a random item from the cache.
    • TTLCache: Time-To-Live. Items expire after a fixed duration (ttl).
    • TLRUCache: Time-aware Least Recently Used. Uses a time-to-use (ttu) function to determine expiration.
  7. Use a shared cache for multiple functions

    master

    You can use a single dictionary or cache object for multiple functions. To prevent collisions between different functions that might receive the same arguments, use the key parameter with functools.partial and cachetools.keys.hashkey to prefix the keys with a function identifier.

    from cachetools.keys import hashkey
    from functools import partial
    
    # shared cache
    numcache = {}
    
    @cached(numcache, key=partial(hashkey, 'fib'))
    def fib(n):
       return n if n < 2 else fib(n - 1) + fib(n - 2)
    
    @cached(numcache, key=partial(hashkey, 'luc'))
    def luc(n):
       return 2 - n if n < 2 else luc(n - 1) + luc(n - 2)
    
    # Keys in numcache will look like: ('fib', n) and ('luc', n)
  8. Use TLRUCache for Time-To-Use expiration

    master

    Use TLRUCache when expiration time is calculated dynamically per item using a user-provided ttu function. The ttu function is called at insertion with (key, value, timer()) and must return a value comparable against later timer() results.

    Configuration:

    • ttu: A callable (key, value, now) -> expiration_time.
    • timer: A callable to retrieve the current time.
    • expire(time=None): Manually removes expired items. Returns an iterable of (key, value) pairs.
    # Example with numeric TTL
    def my_ttu(_key, value, now):
        return now + value.ttu
    
    cache = TLRUCache(maxsize=10, ttu=my_ttu)
    
    # Example with datetime
    from datetime import datetime, timedelta
    
    def datetime_ttu(_key, value, now):
        return now + timedelta(hours=value.hours)
    
    cache = TLRUCache(maxsize=10, ttu=datetime_ttu, timer=datetime.now)
  9. Use memoizing decorators from cachetools.func

    master

    The cachetools.func module provides decorators compatible with Python's functools.lru_cache API. These decorators wrap functions with a memoizing callable that saves up to maxsize results using various algorithms. All decorators are thread-safe by default.

    Common Options:

    • maxsize: The maximum number of items to cache. If set to None, the cache grows without bound.
    • typed: If True, arguments of different types are cached separately (e.g., f(3) and f(3.0) are distinct).

    Instrumentation: Wrapped functions include the following utility functions:

    • cache_parameters(): Returns a dict of maxsize and typed values.
    • cache_info(): Provides information about cache performance (similar to functools.lru_cache).
    • cache_clear(): Clears the cache.
    @cachetools.func.lru_cache
    def count_vowels(sentence):
        sentence = sentence.casefold()
        return sum(sentence.count(vowel) for vowel in 'aeiou')
  10. Use LRUCache for least recently used eviction

    master

    The LRUCache class implements a cache that discards the least recently used items when it reaches its maxsize. This is useful for limiting memory usage while keeping frequently accessed items available.

    from cachetools import cached, LRUCache
    
    @cached(cache=LRUCache(maxsize=32))
    def get_pep(num):
        # Implementation that fetches data
        pass