[Python-checkins] gh-106292: restore checking __dict__ in cached_property.__get__ (#106380)

carljm webhook-mailer at python.org
Wed Jul 5 19:01:40 EDT 2023


https://github.com/python/cpython/commit/838406b4fc044c0b2f397c23275c69f16a76205b
commit: 838406b4fc044c0b2f397c23275c69f16a76205b
branch: main
author: Carl Meyer <carl at oddbird.net>
committer: carljm <carl at oddbird.net>
date: 2023-07-05T17:01:35-06:00
summary:

gh-106292: restore checking __dict__ in cached_property.__get__ (#106380)

* gh-106292: restore checking __dict__ in cached_property.__get__

Co-authored-by: Dong-hee Na <donghee.na92 at gmail.com>

files:
A Misc/NEWS.d/next/Library/2023-07-03-15-09-44.gh-issue-106292.3npldV.rst
M Lib/functools.py
M Lib/test/test_functools.py

diff --git a/Lib/functools.py b/Lib/functools.py
index 4d5e270900784..8518450a8d499 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -957,9 +957,10 @@ def __isabstractmethod__(self):
 
 
 ################################################################################
-### 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 @@ def __get__(self, instance, owner=None):
                 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 d668fa4c3adf5..c4eca0f5b7951 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -3037,6 +3037,25 @@ def test_access_from_class(self):
     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 0000000000000..233509344d509
--- /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.



More information about the Python-checkins mailing list