################################################################################
-### cached_property() - computed once per instance, cached as attribute
+### cached_property() - property result cached as instance attribute
################################################################################
+_NOT_FOUND = object()
class cached_property:
def __init__(self, func):
f"instance to cache {self.attrname!r} property."
)
raise TypeError(msg) from None
- val = self.func(instance)
- try:
- cache[self.attrname] = val
- except TypeError:
- msg = (
- f"The '__dict__' attribute on {type(instance).__name__!r} instance "
- f"does not support item assignment for caching {self.attrname!r} property."
- )
- raise TypeError(msg) from None
+ val = cache.get(self.attrname, _NOT_FOUND)
+ if val is _NOT_FOUND:
+ val = self.func(instance)
+ try:
+ cache[self.attrname] = val
+ except TypeError:
+ msg = (
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance "
+ f"does not support item assignment for caching {self.attrname!r} property."
+ )
+ raise TypeError(msg) from None
return val
__class_getitem__ = classmethod(GenericAlias)
def test_doc(self):
self.assertEqual(CachedCostItem.cost.__doc__, "The cost of the item.")
+ def test_subclass_with___set__(self):
+ """Caching still works for a subclass defining __set__."""
+ class readonly_cached_property(py_functools.cached_property):
+ def __set__(self, obj, value):
+ raise AttributeError("read only property")
+
+ class Test:
+ def __init__(self, prop):
+ self._prop = prop
+
+ @readonly_cached_property
+ def prop(self):
+ return self._prop
+
+ t = Test(1)
+ self.assertEqual(t.prop, 1)
+ t._prop = 999
+ self.assertEqual(t.prop, 1)
+
if __name__ == '__main__':
unittest.main()
--- /dev/null
+Check for an instance-dict cached value in the :meth:`__get__` method of
+:func:`functools.cached_property`. This better matches the pre-3.12 behavior
+and improves compatibility for users subclassing
+:func:`functools.cached_property` and adding a :meth:`__set__` method.