Source code for cacheout.lfu
"""The lfu module provides the :class:`LFUCache` (Least Frequently Used) class."""
from collections import Counter
import typing as t
from .cache import T_TTL, Cache, RemovalCause
[docs]
class LFUCache(Cache):
"""
The Least Frequently Used (LFU) cache is like :class:`.Cache` but uses a least-frequently-used
eviction policy.
The primary difference with :class:`.Cache` is that access to cache entries (i.e. calls to
:meth:`get` and :meth:`set`) are tracked; each call to :meth:`get` will increment the cache
key's access count while calls to :meth:`set` will reset the counter. During cache eviction, the
entry with the lowest access count is removed first.
"""
_access_counts: Counter
def setup(self) -> None:
super().setup()
self._access_counts: Counter = Counter()
def __next__(self) -> t.Hashable:
with self._lock:
try:
return self._access_counts.most_common(n=1)[0][0]
except (ValueError, IndexError): # pragma: no cover
# Empty cache.
raise StopIteration
def _touch(self, key: t.Hashable) -> None:
# Decrement access counts so we can use Counter.most_common() to return the least accessed
# keys first (i.e. keys with a higher count).
self._access_counts[key] -= 1
[docs]
def get(self, key: t.Hashable, default: t.Any = None) -> t.Any:
with self._lock:
value = super().get(key, default=default)
if key in self._cache:
self._touch(key)
return value
get.__doc__ = Cache.get.__doc__
[docs]
def set(self, key: t.Hashable, value: t.Any, ttl: t.Optional[T_TTL] = None) -> None:
with self._lock:
super().set(key, value, ttl=ttl)
self._touch(key)
set.__doc__ = Cache.set.__doc__
[docs]
def add(self, key: t.Hashable, value: t.Any, ttl: t.Optional[T_TTL] = None) -> None:
with self._lock:
super().add(key, value, ttl=ttl)
self._touch(key)
add.__doc__ = Cache.add.__doc__
def _delete(self, key: t.Hashable, cause: t.Optional[RemovalCause] = None) -> int:
count = super()._delete(key, cause)
try:
del self._access_counts[key]
except KeyError: # pragma: no cover
pass
return count
def _clear(self) -> None:
super()._clear()
self._access_counts.clear()