Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wrapt/caching.py: 24%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Caching decorators. Currently provides ``lru_cache``, a drop-in
2replacement for ``functools.lru_cache`` with correct handling of instance
3methods: a separate cache is maintained per instance, stored as an
4attribute on the instance itself so it is cleaned up with the instance
5by the garbage collector. For plain functions, class methods, and static
6methods a single shared cache is used, matching ``functools.lru_cache``.
7"""
9from functools import lru_cache as _functools_lru_cache
10from functools import partial
12from .__wrapt__ import BaseObjectProxy, BoundFunctionWrapper, FunctionWrapper
13from .decorators import decorator
14from .synchronization import synchronized
16# Decorator that applies functools.lru_cache to the wrapped function.
17# Unlike using functools.lru_cache directly, this works correctly with
18# instance methods and class methods by maintaining a separate cache
19# per instance or class. Each per-instance cache is stored as an
20# attribute on the instance itself, so it is automatically cleaned up
21# when the instance is garbage collected. For plain functions and
22# static methods, a single cache is stored on the wrapper itself.
23# The underlying caches are created lazily on first call, with
24# double-checked locking via synchronized to avoid races in
25# multi-threaded code.
26#
27# Custom FunctionWrapper and BoundFunctionWrapper subclasses are used
28# so that cache_info() and cache_clear() are available directly on
29# the decorated function. For bound methods, the BoundFunctionWrapper
30# uses _self_instance to locate the per-instance cache.
33class _BoundLRUCacheFunctionWrapper(BoundFunctionWrapper):
35 def _is_instance_method(self):
36 return self._self_binding == "function"
38 def __call__(self, *args, **kwargs):
39 parent = self._self_parent
41 # For class methods and static methods, use a single shared
42 # cache stored on the parent wrapper. The cache is created
43 # from the bound __wrapped__ since the parent's __wrapped__
44 # is the raw classmethod/staticmethod descriptor.
46 if not self._is_instance_method():
47 if parent._self_cache is None:
48 with synchronized(parent):
49 if parent._self_cache is None:
50 parent._self_cache = _functools_lru_cache(
51 **parent._self_lru_kwargs
52 )(self.__wrapped__)
54 return parent._self_cache(*args, **kwargs)
56 # Instance method — per-instance cache stored as an attribute
57 # on the instance so it is cleaned up with the instance by the
58 # garbage collector.
60 instance = self._self_instance
61 cache_attr = parent._self_cache_attr
63 cache = getattr(instance, cache_attr, None)
65 if cache is None:
66 with synchronized(parent):
67 cache = getattr(instance, cache_attr, None)
69 if cache is None:
70 cache = _functools_lru_cache(**parent._self_lru_kwargs)(
71 self.__wrapped__
72 )
74 # If the instance the method is bound to is a wrapt
75 # object proxy, a plain setattr() would fall through and
76 # store the cache on the wrapped object rather than the
77 # proxy. Use type() rather than isinstance() so the check
78 # sees the real proxy type and is not fooled by the proxy
79 # overriding __class__ to report the wrapped object's
80 # type. Proxies expose __self_setattr__() which stores the
81 # attribute on the proxy itself.
83 if issubclass(type(instance), BaseObjectProxy):
84 instance.__self_setattr__(cache_attr, cache)
85 else:
86 setattr(instance, cache_attr, cache)
88 return cache(*args, **kwargs)
90 def cache_info(self):
91 """Return the cache statistics for this binding's cache, or
92 ``None`` if the cache has not yet been created.
93 """
95 if not self._is_instance_method():
96 return self._self_parent.cache_info()
98 cache = getattr(self._self_instance, self._self_parent._self_cache_attr, None)
100 if cache is not None:
101 return cache.cache_info()
103 return None
105 def cache_clear(self):
106 """Clear this binding's cache."""
108 if not self._is_instance_method():
109 self._self_parent.cache_clear()
110 return
112 cache = getattr(self._self_instance, self._self_parent._self_cache_attr, None)
114 if cache is not None:
115 cache.cache_clear()
117 def cache_parameters(self):
118 """Return the parameters used to create the cache."""
120 if not self._is_instance_method():
121 return self._self_parent.cache_parameters()
123 cache = getattr(self._self_instance, self._self_parent._self_cache_attr, None)
125 if cache is not None:
126 return cache.cache_parameters()
128 return None
131class _LRUCacheFunctionWrapper(FunctionWrapper):
133 __bound_function_wrapper__ = _BoundLRUCacheFunctionWrapper
135 def __init__(self, wrapped, wrapper, **kwargs):
136 super().__init__(wrapped, wrapper, **kwargs)
138 # Extract the LRU cache configuration that was attached to the
139 # wrapper function before it was passed to the decorator.
141 self._self_lru_kwargs = wrapper._self_lru_kwargs
142 self._self_cache = None
144 # Use __func__ to get the name for classmethod/staticmethod
145 # descriptors which lack __name__ on Python < 3.10.
147 name = getattr(wrapped, "__name__", None)
149 if name is None:
150 name = wrapped.__func__.__name__
152 # The cache attribute name must be unique per decorated method so
153 # that a method overridden in a subclass does not share the same
154 # per-instance cache slot as the method it overrides. If they shared
155 # a slot, a subclass method calling super() would find the subclass
156 # cache and re-enter its own body, recursing forever. The owning
157 # class is not known here, since the decorator runs on the raw
158 # function before the class exists, but each decorated method has its
159 # own wrapper instance, so id(self) is a stable discriminator unique
160 # to this method definition.
162 self._self_cache_attr = "_lru_cache_" + name + "_" + str(id(self))
164 def __call__(self, *args, **kwargs):
165 # Plain function or static method — single cache stored
166 # on the wrapper itself.
168 if self._self_cache is None:
169 with synchronized(self):
170 if self._self_cache is None:
171 self._self_cache = _functools_lru_cache(**self._self_lru_kwargs)(
172 self.__wrapped__
173 )
175 return self._self_cache(*args, **kwargs)
177 def cache_info(self):
178 """Return the cache statistics, or ``None`` if the cache has
179 not yet been created.
180 """
182 if self._self_cache is not None:
183 return self._self_cache.cache_info()
185 return None
187 def cache_clear(self):
188 """Clear the cache and reset the statistics."""
190 if self._self_cache is not None:
191 self._self_cache.cache_clear()
193 def cache_parameters(self):
194 """Return the parameters used to create the cache, or ``None``
195 if the cache has not yet been created.
196 """
198 if self._self_cache is not None:
199 return self._self_cache.cache_parameters()
201 return None
204def lru_cache(func=None, /, **kwargs):
205 """A decorator that applies ``functools.lru_cache`` to the wrapped
206 function, with correct handling for instance methods, class methods,
207 and static methods.
209 For instance methods, a separate ``functools.lru_cache`` is
210 maintained per instance. The cache is stored as an attribute on the
211 instance itself, so it is automatically cleaned up when the instance
212 is garbage collected. This means instances do not need to be
213 hashable, each instance gets its own full ``maxsize`` budget, and
214 no external mapping prevents garbage collection.
216 For plain functions, class methods, and static methods, a single
217 shared cache is used.
219 All keyword arguments are passed through to ``functools.lru_cache``.
221 Cache management methods ``cache_info()`` and ``cache_clear()`` are
222 available directly on the decorated function. For bound methods,
223 these operate on the per-instance cache for the bound instance.
224 """
226 if func is None:
227 return partial(lru_cache, **kwargs)
229 def _wrapper(wrapped, instance, args, _kwargs):
230 return wrapped(*args, **_kwargs)
232 _wrapper._self_lru_kwargs = kwargs
234 return decorator(_wrapper, proxy=_LRUCacheFunctionWrapper)(func)