Skip to content
Merged
Prev Previous commit
Clearer error message when __dict__ is not mutable.
  • Loading branch information
carljm committed Aug 19, 2018
commit 5bdeaab770692de4cb58f43746ba09c024897232
7 changes: 6 additions & 1 deletion Doc/library/functools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ The :mod:`functools` module defines the following functions:

.. note::

This decorator does not support classes which define ``__slots__``.
This decorator requires that the ``__dict__`` attribute on each instance
be a mutable mapping. This means it will not work with some types, such as
metaclasses (since the ``__dict__`` attributes on type instances are
read-only proxies for the class namespace), and those that specify
``__slots__`` without including ``__dict__`` as one of the defined slots
(as such classes don't provide a ``__dict__`` attribute at all).


.. function:: cmp_to_key(func)
Expand Down
12 changes: 10 additions & 2 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ def __get__(self, instance, owner):
"Cannot use cached_property instance without calling __set_name__ on it.")
try:
cache = instance.__dict__
except AttributeError: # objects with __slots__ have no __dict__
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
msg = (
f"No '__dict__' attribute on {type(instance).__name__!r} "
f"instance to cache {self.attrname!r} property."
Expand All @@ -913,5 +913,13 @@ def __get__(self, instance, owner):
# check if another thread filled cache while we awaited lock
val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND:
val = cache[self.attrname] = self.func(instance)
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
15 changes: 15 additions & 0 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,21 @@ def test_object_with_slots(self):
):
item.cost

def test_immutable_dict(self):
class MyMeta(type):
@py_functools.cached_property
def prop(self):
return True

class MyClass(metaclass=MyMeta):
pass

with self.assertRaisesRegex(
TypeError,
"The '__dict__' attribute on 'MyMeta' instance does not support item assignment for caching 'prop' property.",
):
MyClass.prop

def test_reuse_different_names(self):
"""Disallow this case because decorated function a would not be cached."""
with self.assertRaises(RuntimeError) as ctx:
Expand Down