Source code for stouputils.decorators.simple_cache
# Imports
from collections.abc import Callable
from functools import wraps
from pickle import dumps as pickle_dumps
from typing import Any, Literal, overload
from .common import get_wrapper_name, set_wrapper_name
# Easy cache function with parameter caching method
@overload
def simple_cache[T](
func: Callable[..., T],
*,
method: Literal["str", "pickle"] = "str"
) -> Callable[..., T]: ...
@overload
def simple_cache[T](
func: None = None,
*,
method: Literal["str", "pickle"] = "str"
) -> Callable[[Callable[..., T]], Callable[..., T]]: ...
[docs]
def simple_cache[T](
func: Callable[..., T] | None = None,
*,
method: Literal["str", "pickle"] = "str"
) -> Callable[..., T] | Callable[[Callable[..., T]], Callable[..., T]]:
""" Decorator that caches the result of a function based on its arguments.
The str method is often faster than the pickle method (by a little) but not as accurate with complex objects.
Args:
func (Callable[..., T] | None): Function to cache
method (Literal["str", "pickle"]): The method to use for caching.
Examples:
>>> @simple_cache
... def test1(a: int, b: int) -> int:
... return a + b
>>> @simple_cache(method="str")
... def test2(a: int, b: int) -> int:
... return a + b
>>> test2(1, 2)
3
>>> test2(1, 2)
3
>>> test2(3, 4)
7
>>> @simple_cache
... def factorial(n: int) -> int:
... return n * factorial(n - 1) if n else 1
>>> factorial(10) # no previously cached result, makes 11 recursive calls
3628800
>>> factorial(5) # no new calls, just returns the cached result
120
>>> factorial(12) # two new recursive calls, factorial(10) is cached
479001600
"""
def decorator(func: Callable[..., T]) -> Callable[..., T]:
# Create the cache dict
cache_dict: dict[Any, Any] = {}
# Create the wrapper
@wraps(func)
def wrapper(*args: tuple[Any, ...], **kwargs: dict[str, Any]) -> Any:
# Get the hashed key
if method == "str":
hashed = str(args) + str(kwargs)
elif method == "pickle":
hashed = pickle_dumps((args, kwargs))
else:
raise ValueError("Invalid caching method. Supported methods are 'str' and 'pickle'.")
# If the key is in the cache, return it
if hashed in cache_dict:
return cache_dict[hashed]
# Else, call the function and add the result to the cache
else:
result: Any = func(*args, **kwargs)
cache_dict[hashed] = result
return result
# Return the wrapper
set_wrapper_name(wrapper, get_wrapper_name("stouputils.decorators.simple_cache", func))
return wrapper
# Handle both @simple_cache and @simple_cache(method=...)
if func is None:
return decorator
return decorator(func)