|
1 | | -from collections.abc import Hashable |
2 | | -from functools import partial |
| 1 | +from collections.abc import Callable, Hashable |
| 2 | +from functools import lru_cache, partial |
3 | 3 | from itertools import tee |
| 4 | +from typing import TypeVar |
4 | 5 |
|
5 | | -__all__ = ['memoized_func', 'memoized_meth', 'memoized_generator'] |
| 6 | +__all__ = ['memoized_func', 'memoized_meth', 'memoized_generator', 'CacheInstances'] |
6 | 7 |
|
7 | 8 |
|
8 | 9 | class memoized_func: |
@@ -125,3 +126,63 @@ def __call__(self, *args, **kwargs): |
125 | 126 | it = cache[key] if key in cache else self.func(*args, **kwargs) |
126 | 127 | cache[key], result = tee(it) |
127 | 128 | return result |
| 129 | + |
| 130 | + |
| 131 | +# Describes the type of a subclass of CacheInstances |
| 132 | +InstanceType = TypeVar('InstanceType', bound='CacheInstances', covariant=True) |
| 133 | + |
| 134 | + |
| 135 | +class CacheInstancesMeta(type): |
| 136 | + """ |
| 137 | + Metaclass to wrap construction in an LRU cache. |
| 138 | + """ |
| 139 | + |
| 140 | + _cached_types: set[type['CacheInstances']] = set() |
| 141 | + |
| 142 | + def __init__(cls: type[InstanceType], *args) -> None: # type: ignore |
| 143 | + super().__init__(*args) |
| 144 | + |
| 145 | + # Register the cached type |
| 146 | + CacheInstancesMeta._cached_types.add(cls) |
| 147 | + |
| 148 | + def __call__(cls: type[InstanceType], # type: ignore |
| 149 | + *args, **kwargs) -> InstanceType: |
| 150 | + if cls._instance_cache is None: |
| 151 | + maxsize = cls._instance_cache_size |
| 152 | + cls._instance_cache = lru_cache(maxsize=maxsize)(super().__call__) |
| 153 | + |
| 154 | + args, kwargs = cls._preprocess_args(*args, **kwargs) |
| 155 | + return cls._instance_cache(*args, **kwargs) |
| 156 | + |
| 157 | + @classmethod |
| 158 | + def clear_caches(cls: type['CacheInstancesMeta']) -> None: |
| 159 | + """ |
| 160 | + Clear all caches for classes using this metaclass. |
| 161 | + """ |
| 162 | + for cached_type in cls._cached_types: |
| 163 | + if cached_type._instance_cache is not None: |
| 164 | + cached_type._instance_cache.cache_clear() |
| 165 | + |
| 166 | + |
| 167 | +class CacheInstances(metaclass=CacheInstancesMeta): |
| 168 | + """ |
| 169 | + Parent class that wraps construction in an LRU cache. |
| 170 | + """ |
| 171 | + |
| 172 | + _instance_cache: Callable | None = None |
| 173 | + _instance_cache_size: int = 128 |
| 174 | + |
| 175 | + @classmethod |
| 176 | + def _preprocess_args(cls, *args, **kwargs): |
| 177 | + """ |
| 178 | + Preprocess the arguments before caching. This can be overridden in subclasses |
| 179 | + to customize argument handling (e.g. to convert to hashable types). |
| 180 | + """ |
| 181 | + return args, kwargs |
| 182 | + |
| 183 | + @staticmethod |
| 184 | + def clear_caches() -> None: |
| 185 | + """ |
| 186 | + Clears all IR instance caches. |
| 187 | + """ |
| 188 | + CacheInstancesMeta.clear_caches() |
0 commit comments