Skip to content

Commit

Permalink
[3.12] gh-106292: restore checking __dict__ in cached_property.__get__ (
Browse files Browse the repository at this point in the history
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 <carl@oddbird.net>
Co-authored-by: Dong-hee Na <donghee.na92@gmail.com>
  • Loading branch information
3 people committed Jul 5, 2023
1 parent bb17e6f commit 7b615a1
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 10 deletions.
23 changes: 13 additions & 10 deletions Lib/functools.py
Expand Up @@ -956,9 +956,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):
Expand Down Expand Up @@ -989,15 +990,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)
19 changes: 19 additions & 0 deletions Lib/test/test_functools.py
Expand Up @@ -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()
@@ -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.

0 comments on commit 7b615a1

Please sign in to comment.