Thread-safe LRUCache based on an OrderedDict. All dict operations (__getitem__, __setitem__, __contains__) update the priority of the relevant key and take O(1) time. The dict is iterated over in order from the oldest to newest key, which means that a complete pass over the dict sho
| 10 | |
| 11 | |
| 12 | class LRUCache(MutableMapping[K, V]): |
| 13 | """Thread-safe LRUCache based on an OrderedDict. |
| 14 | |
| 15 | All dict operations (__getitem__, __setitem__, __contains__) update the |
| 16 | priority of the relevant key and take O(1) time. The dict is iterated over |
| 17 | in order from the oldest to newest key, which means that a complete pass |
| 18 | over the dict should not affect the order of any entries. |
| 19 | |
| 20 | When a new item is set and the maximum size of the cache is exceeded, the |
| 21 | oldest item is dropped and called with ``on_evict(key, value)``. |
| 22 | |
| 23 | The ``maxsize`` property can be used to view or adjust the capacity of |
| 24 | the cache, e.g., ``cache.maxsize = new_size``. |
| 25 | """ |
| 26 | |
| 27 | _cache: OrderedDict[K, V] |
| 28 | _maxsize: int |
| 29 | _lock: threading.RLock |
| 30 | _on_evict: Callable[[K, V], Any] | None |
| 31 | |
| 32 | __slots__ = ("_cache", "_lock", "_maxsize", "_on_evict") |
| 33 | |
| 34 | def __init__(self, maxsize: int, on_evict: Callable[[K, V], Any] | None = None): |
| 35 | """ |
| 36 | Parameters |
| 37 | ---------- |
| 38 | maxsize : int |
| 39 | Integer maximum number of items to hold in the cache. |
| 40 | on_evict : callable, optional |
| 41 | Function to call like ``on_evict(key, value)`` when items are |
| 42 | evicted. |
| 43 | """ |
| 44 | if not isinstance(maxsize, int): |
| 45 | raise TypeError("maxsize must be an integer") |
| 46 | if maxsize < 0: |
| 47 | raise ValueError("maxsize must be non-negative") |
| 48 | self._maxsize = maxsize |
| 49 | self._cache = OrderedDict() |
| 50 | self._lock = threading.RLock() |
| 51 | self._on_evict = on_evict |
| 52 | |
| 53 | def __getitem__(self, key: K) -> V: |
| 54 | # record recent use of the key by moving it to the front of the list |
| 55 | with self._lock: |
| 56 | value = self._cache[key] |
| 57 | self._cache.move_to_end(key) |
| 58 | return value |
| 59 | |
| 60 | def _enforce_size_limit(self, capacity: int) -> None: |
| 61 | """Shrink the cache if necessary, evicting the oldest items.""" |
| 62 | while len(self._cache) > capacity: |
| 63 | key, value = self._cache.popitem(last=False) |
| 64 | if self._on_evict is not None: |
| 65 | self._on_evict(key, value) |
| 66 | |
| 67 | def __setitem__(self, key: K, value: V) -> None: |
| 68 | with self._lock: |
| 69 | if key in self._cache: |
no outgoing calls
searching dependent graphs…