Skip to content

Commit 7b615a1

Browse files
miss-islingtoncarljmcorona10
authored
[3.12] gh-106292: restore checking __dict__ in cached_property.__get__ (GH-106380) (#106469)
gh-106292: restore checking __dict__ in cached_property.__get__ (GH-106380) * gh-106292: restore checking __dict__ in cached_property.__get__ (cherry picked from commit 838406b) Co-authored-by: Carl Meyer <[email protected]> Co-authored-by: Dong-hee Na <[email protected]>
1 parent bb17e6f commit 7b615a1

File tree

3 files changed

+36
-10
lines changed

3 files changed

+36
-10
lines changed

Lib/functools.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -956,9 +956,10 @@ def __isabstractmethod__(self):
956956

957957

958958
################################################################################
959-
### cached_property() - computed once per instance, cached as attribute
959+
### cached_property() - property result cached as instance attribute
960960
################################################################################
961961

962+
_NOT_FOUND = object()
962963

963964
class cached_property:
964965
def __init__(self, func):
@@ -989,15 +990,17 @@ def __get__(self, instance, owner=None):
989990
f"instance to cache {self.attrname!r} property."
990991
)
991992
raise TypeError(msg) from None
992-
val = self.func(instance)
993-
try:
994-
cache[self.attrname] = val
995-
except TypeError:
996-
msg = (
997-
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
998-
f"does not support item assignment for caching {self.attrname!r} property."
999-
)
1000-
raise TypeError(msg) from None
993+
val = cache.get(self.attrname, _NOT_FOUND)
994+
if val is _NOT_FOUND:
995+
val = self.func(instance)
996+
try:
997+
cache[self.attrname] = val
998+
except TypeError:
999+
msg = (
1000+
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
1001+
f"does not support item assignment for caching {self.attrname!r} property."
1002+
)
1003+
raise TypeError(msg) from None
10011004
return val
10021005

10031006
__class_getitem__ = classmethod(GenericAlias)

Lib/test/test_functools.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3037,6 +3037,25 @@ def test_access_from_class(self):
30373037
def test_doc(self):
30383038
self.assertEqual(CachedCostItem.cost.__doc__, "The cost of the item.")
30393039

3040+
def test_subclass_with___set__(self):
3041+
"""Caching still works for a subclass defining __set__."""
3042+
class readonly_cached_property(py_functools.cached_property):
3043+
def __set__(self, obj, value):
3044+
raise AttributeError("read only property")
3045+
3046+
class Test:
3047+
def __init__(self, prop):
3048+
self._prop = prop
3049+
3050+
@readonly_cached_property
3051+
def prop(self):
3052+
return self._prop
3053+
3054+
t = Test(1)
3055+
self.assertEqual(t.prop, 1)
3056+
t._prop = 999
3057+
self.assertEqual(t.prop, 1)
3058+
30403059

30413060
if __name__ == '__main__':
30423061
unittest.main()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Check for an instance-dict cached value in the :meth:`__get__` method of
2+
:func:`functools.cached_property`. This better matches the pre-3.12 behavior
3+
and improves compatibility for users subclassing
4+
:func:`functools.cached_property` and adding a :meth:`__set__` method.

0 commit comments

Comments
 (0)