summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/functools.py23
-rw-r--r--Lib/test/test_functools.py19
-rw-r--r--Misc/NEWS.d/next/Library/2023-07-03-15-09-44.gh-issue-106292.3npldV.rst4
3 files changed, 36 insertions, 10 deletions
diff --git a/Lib/functools.py b/Lib/functools.py
index 4d5e270..8518450 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -957,9 +957,10 @@ class singledispatchmethod:
################################################################################
-### 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):
@@ -990,15 +991,17 @@ class cached_property:
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)
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index d668fa4..c4eca0f 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -3037,6 +3037,25 @@ class TestCachedProperty(unittest.TestCase):
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()
diff --git a/Misc/NEWS.d/next/Library/2023-07-03-15-09-44.gh-issue-106292.3npldV.rst b/Misc/NEWS.d/next/Library/2023-07-03-15-09-44.gh-issue-106292.3npldV.rst
new file mode 100644
index 0000000..2335093
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-07-03-15-09-44.gh-issue-106292.3npldV.rst
@@ -0,0 +1,4 @@
+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.