]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-106292: restore checking __dict__ in cached_property.__get__ (#106380)
authorCarl Meyer <carl@oddbird.net>
Wed, 5 Jul 2023 23:01:35 +0000 (17:01 -0600)
committerGitHub <noreply@github.com>
Wed, 5 Jul 2023 23:01:35 +0000 (17:01 -0600)
* gh-106292: restore checking __dict__ in cached_property.__get__

Co-authored-by: Dong-hee Na <donghee.na92@gmail.com>
Lib/functools.py
Lib/test/test_functools.py
Misc/NEWS.d/next/Library/2023-07-03-15-09-44.gh-issue-106292.3npldV.rst [new file with mode: 0644]

index 4d5e2709007843d7682e67c47263bd8f37a505aa..8518450a8d499dbf5e00f1d0d801c813cd01f05b 100644 (file)
@@ -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)
index d668fa4c3adf5c2540040ac299b0639324cde6c2..c4eca0f5b795117eb8a9949509d543adfee855e4 100644 (file)
@@ -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 (file)
index 0000000..2335093
--- /dev/null
@@ -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.