11"""Method decorator helpers."""
22
3+ __all__ = ()
4+
35import functools
46import warnings
57import weakref
68
79
8- def warn_classmethod (stacklevel ):
10+ def _warn_classmethod (stacklevel ):
911 warnings .warn (
1012 "decorating class methods with @cachedmethod is deprecated" ,
1113 DeprecationWarning ,
1214 stacklevel = stacklevel ,
1315 )
1416
1517
16- def warn_instance_dict (msg , stacklevel ):
18+ def _warn_instance_dict (msg , stacklevel ):
1719 warnings .warn (
1820 msg ,
1921 DeprecationWarning ,
2022 stacklevel = stacklevel ,
2123 )
2224
2325
24- class WrapperBase :
26+ class _WrapperBase :
27+ """Wrapper base class providing default implementations for properties."""
28+
2529 def __init__ (self , obj , method , cache , key , lock = None , cond = None ):
2630 if isinstance (obj , type ):
27- warn_classmethod (stacklevel = 5 )
31+ _warn_classmethod (stacklevel = 5 )
2832 functools .update_wrapper (self , method )
2933 self ._obj = obj # protected
3034 self .__cache = cache
@@ -55,10 +59,12 @@ def cache_condition(self):
5559 return None if self .__cond is None else self .__cond (self ._obj )
5660
5761
58- class DescriptorBase :
59- def __init__ (self , warndict = False ):
62+ class _DescriptorBase :
63+ """Descriptor base class implementing the basic descriptor protocol."""
64+
65+ def __init__ (self , deprecated = False ):
6066 self .__attrname = None
61- self .__warndict = warndict
67+ self .__deprecated = deprecated
6268
6369 def __set_name__ (self , owner , name ):
6470 if self .__attrname is None :
@@ -72,16 +78,19 @@ def __set_name__(self, owner, name):
7278 def __get__ (self , obj , objtype = None ):
7379 wrapper = self .Wrapper (obj )
7480 if self .__attrname is not None :
81+ # replace descriptor instance with wrapper in instance dict
7582 try :
83+ # In case of a race condition where another thread already replaced
84+ # the descriptor, prefer the initial wrapper.
7685 wrapper = obj .__dict__ .setdefault (self .__attrname , wrapper )
7786 except AttributeError :
7887 # not all objects have __dict__ (e.g. class defines slots)
7988 msg = (
8089 f"No '__dict__' attribute on { type (obj ).__name__ !r} "
8190 f"instance to cache { self .__attrname !r} property."
8291 )
83- if self .__warndict :
84- warn_instance_dict (msg , 3 )
92+ if self .__deprecated :
93+ _warn_instance_dict (msg , 3 )
8594 else :
8695 raise TypeError (msg ) from None
8796 except TypeError : # pragma: no cover
@@ -90,39 +99,46 @@ def __get__(self, obj, objtype=None):
9099 f"instance does not support item assignment for "
91100 f"caching { self .__attrname !r} property."
92101 )
93- if self .__warndict :
94- warn_instance_dict (msg , 3 )
102+ if self .__deprecated :
103+ _warn_instance_dict (msg , 3 )
95104 else :
96105 raise TypeError (msg ) from None
97- elif not self .__warndict : # pragma: no cover
98- msg = (
99- "Cannot use @cachedmethod instance without "
100- "calling __set_name__ on it"
101- )
106+ elif self .__deprecated :
107+ pass # deprecated @classmethod, warning already raised elsewhere
108+ else : # pragma: no cover
109+ msg = "Cannot use @cachedmethod instance without calling __set_name__ on it"
102110 raise TypeError (msg ) from None
103111 return wrapper
104112
105113
106- class DeprecatedDescriptorBase (DescriptorBase ):
114+ class _DeprecatedDescriptorBase (_DescriptorBase ):
115+ """Descriptor base class supporting deprecated @classmethod use."""
116+
107117 def __init__ (self , wrapper , cache_clear ):
108- super ().__init__ (True )
118+ super ().__init__ (deprecated = True )
109119 self .__wrapper = wrapper
110120 self .__cache_clear = cache_clear
111121
112122 # called for @classmethod with Python >= 3.13
113123 def __call__ (self , * args , ** kwargs ):
114- warn_classmethod (stacklevel = 3 )
124+ _warn_classmethod (stacklevel = 3 )
115125 return self .__wrapper (* args , ** kwargs )
116126
117127 # backward-compatible @classmethod handling with Python >= 3.13
118128 def cache_clear (self , objtype ):
119- warn_classmethod (stacklevel = 3 )
129+ _warn_classmethod (stacklevel = 3 )
120130 return self .__cache_clear (objtype )
121131
122132
133+ # At least for now, the implementation prefers clarity and performance
134+ # over ease of maintenance, thus providing separate descriptors for
135+ # all valid combinations of decorator parameters lock, condition and
136+ # info.
137+
138+
123139def _condition_info (method , cache , key , lock , cond , info ):
124- class Descriptor (DescriptorBase ):
125- class Wrapper (WrapperBase ):
140+ class Descriptor (_DescriptorBase ):
141+ class Wrapper (_WrapperBase ):
126142 def __init__ (self , obj ):
127143 super ().__init__ (obj , method , cache , key , lock , cond )
128144 self .__hits = self .__misses = 0
@@ -169,8 +185,8 @@ def cache_info(self):
169185
170186
171187def _locked_info (method , cache , key , lock , info ):
172- class Descriptor (DescriptorBase ):
173- class Wrapper (WrapperBase ):
188+ class Descriptor (_DescriptorBase ):
189+ class Wrapper (_WrapperBase ):
174190 def __init__ (self , obj ):
175191 super ().__init__ (obj , method , cache , key , lock )
176192 self .__hits = self .__misses = 0
@@ -209,8 +225,8 @@ def cache_info(self):
209225
210226
211227def _unlocked_info (method , cache , key , info ):
212- class Descriptor (DescriptorBase ):
213- class Wrapper (WrapperBase ):
228+ class Descriptor (_DescriptorBase ):
229+ class Wrapper (_WrapperBase ):
214230 def __init__ (self , obj ):
215231 super ().__init__ (obj , method , cache , key )
216232 self .__hits = self .__misses = 0
@@ -276,8 +292,8 @@ def classmethod_wrapper(self, *args, **kwargs):
276292 p = pending .setdefault (self , set ())
277293 return wrapper (self , p , * args , ** kwargs )
278294
279- class Descriptor (DeprecatedDescriptorBase ):
280- class Wrapper (WrapperBase ):
295+ class Descriptor (_DeprecatedDescriptorBase ):
296+ class Wrapper (_WrapperBase ):
281297 def __init__ (self , obj ):
282298 super ().__init__ (obj , method , cache , key , lock , cond )
283299 self .__pending = set ()
@@ -304,7 +320,9 @@ def wrapper(self, *args, **kwargs):
304320 v = method (self , * args , ** kwargs )
305321 with lock (self ):
306322 try :
307- # possible race condition: see above
323+ # In case of a race condition, i.e. if another thread
324+ # stored a value for this key while we were calling
325+ # method(), prefer the cached value.
308326 return c .setdefault (k , v )
309327 except ValueError :
310328 return v # value too large
@@ -314,8 +332,8 @@ def cache_clear(self):
314332 with lock (self ):
315333 c .clear ()
316334
317- class Descriptor (DeprecatedDescriptorBase ):
318- class Wrapper (WrapperBase ):
335+ class Descriptor (_DeprecatedDescriptorBase ):
336+ class Wrapper (_WrapperBase ):
319337 def __init__ (self , obj ):
320338 super ().__init__ (obj , method , cache , key , lock )
321339
@@ -348,8 +366,8 @@ def cache_clear(self):
348366 c = cache (self )
349367 c .clear ()
350368
351- class Descriptor (DeprecatedDescriptorBase ):
352- class Wrapper (WrapperBase ):
369+ class Descriptor (_DeprecatedDescriptorBase ):
370+ class Wrapper (_WrapperBase ):
353371 def __init__ (self , obj ):
354372 super ().__init__ (obj , method , cache , key )
355373
0 commit comments