Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from collections import deque | |
| from functools import wraps | |
| from typing import Any, Callable, Dict, Generic, Hashable, Tuple, TypeVar, cast | |
| __all__ = [ | |
| "SimpleCache", | |
| "FastDictCache", | |
| "memoized", | |
| ] | |
| _T = TypeVar("_T", bound=Hashable) | |
| _U = TypeVar("_U") | |
| class SimpleCache(Generic[_T, _U]): | |
| """ | |
| Very simple cache that discards the oldest item when the cache size is | |
| exceeded. | |
| :param maxsize: Maximum size of the cache. (Don't make it too big.) | |
| """ | |
| def __init__(self, maxsize: int = 8) -> None: | |
| assert maxsize > 0 | |
| self._data: dict[_T, _U] = {} | |
| self._keys: deque[_T] = deque() | |
| self.maxsize: int = maxsize | |
| def get(self, key: _T, getter_func: Callable[[], _U]) -> _U: | |
| """ | |
| Get object from the cache. | |
| If not found, call `getter_func` to resolve it, and put that on the top | |
| of the cache instead. | |
| """ | |
| # Look in cache first. | |
| try: | |
| return self._data[key] | |
| except KeyError: | |
| # Not found? Get it. | |
| value = getter_func() | |
| self._data[key] = value | |
| self._keys.append(key) | |
| # Remove the oldest key when the size is exceeded. | |
| if len(self._data) > self.maxsize: | |
| key_to_remove = self._keys.popleft() | |
| if key_to_remove in self._data: | |
| del self._data[key_to_remove] | |
| return value | |
| def clear(self) -> None: | |
| "Clear cache." | |
| self._data = {} | |
| self._keys = deque() | |
| _K = TypeVar("_K", bound=Tuple[Hashable, ...]) | |
| _V = TypeVar("_V") | |
| class FastDictCache(Dict[_K, _V]): | |
| """ | |
| Fast, lightweight cache which keeps at most `size` items. | |
| It will discard the oldest items in the cache first. | |
| The cache is a dictionary, which doesn't keep track of access counts. | |
| It is perfect to cache little immutable objects which are not expensive to | |
| create, but where a dictionary lookup is still much faster than an object | |
| instantiation. | |
| :param get_value: Callable that's called in case of a missing key. | |
| """ | |
| # NOTE: This cache is used to cache `prompt_toolkit.layout.screen.Char` and | |
| # `prompt_toolkit.Document`. Make sure to keep this really lightweight. | |
| # Accessing the cache should stay faster than instantiating new | |
| # objects. | |
| # (Dictionary lookups are really fast.) | |
| # SimpleCache is still required for cases where the cache key is not | |
| # the same as the arguments given to the function that creates the | |
| # value.) | |
| def __init__(self, get_value: Callable[..., _V], size: int = 1000000) -> None: | |
| assert size > 0 | |
| self._keys: deque[_K] = deque() | |
| self.get_value = get_value | |
| self.size = size | |
| def __missing__(self, key: _K) -> _V: | |
| # Remove the oldest key when the size is exceeded. | |
| if len(self) > self.size: | |
| key_to_remove = self._keys.popleft() | |
| if key_to_remove in self: | |
| del self[key_to_remove] | |
| result = self.get_value(*key) | |
| self[key] = result | |
| self._keys.append(key) | |
| return result | |
| _F = TypeVar("_F", bound=Callable[..., object]) | |
| def memoized(maxsize: int = 1024) -> Callable[[_F], _F]: | |
| """ | |
| Memoization decorator for immutable classes and pure functions. | |
| """ | |
| def decorator(obj: _F) -> _F: | |
| cache: SimpleCache[Hashable, Any] = SimpleCache(maxsize=maxsize) | |
| def new_callable(*a: Any, **kw: Any) -> Any: | |
| def create_new() -> Any: | |
| return obj(*a, **kw) | |
| key = (a, tuple(sorted(kw.items()))) | |
| return cache.get(key, create_new) | |
| return cast(_F, new_callable) | |
| return decorator | |