Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Caching Computed Attributes

00:00 Caching Computed Attributes. Sometimes you have a given computed attribute that you use frequently. Constantly repeating that same computation may be unnecessary and expensive. To work around this problem, you can cache the computed value and save it in a non-public dedicated attribute for further reuse.

00:23 To prevent unexpected behavior, you need to think of the mutability of the input data. If you have a property that computes its value from a constant input value, then the result will never change. In that case, you can compute the value just once.

01:18 While this implementation of Circle properly caches the computed diameter, it has the drawback that if you ever change the value of .radius, then .diameter won’t return a correct value.

01:34 In this example, you create a circle with a radius equal to 42.0. The .diameter property only computes the value the first time you access it.

01:46 That’s why you see a delay in the first execution and no delay in the second. Note that even though you change the value of .radius, the diameter has stayed the same.

02:05 If the input value for a computed attribute mutates, then you need to recalculate the attribute.

02:35 Here, the setter method of the .radius property resets the private ._diameter to None every time you change the value of .radius. With this update, .diameter recalculates its value the first time you access it after every mutation of .radius.

03:36 As you can see, Circle works correctly now. It computes the diameter the first time you access it and also every time you change the radius.

03:47 Another option to create cached properties is to use functools.cached_property() from the standard library. This function works as a decorator that allows you to transform a method into a cached property.

04:00 The property computes its value only once and caches it as a normal attribute during the lifetime of the instance.

04:27 Here, .diameter computes and caches its value the first time you access it. This kind of implementation is suitable for those computations in which the input values don’t mutate.

04:43 Again, here, you can see it in action. When you access .diameter, you get its computed value. That value remains the same from this point on. However, unlike property(), cached_property() doesn’t block attribute mutations unless you provide a proper setter method.

05:08 This can be seen here, as it’s possible to update the diameter to 200. If you want to create a cached property that doesn’t allow modification, then you can use property() and functools.cache(), as seen in the following example on-screen.

05:47 Here, you stack the @property decorator on top of the @cache decorator. The combination of both decorators builds a cached property that prevents mutations.

06:25 Here, when you try to assign a new value to .diameter, you get an AttributeError because the setter functionality comes from the internal descriptor of property.

06:37 In the next section of the course, you’ll see how to log when an attribute is accessed or mutated.

Avatar image for matheorism

matheorism on Dec. 29, 2023

Hi Darren,

I’ve noticed on circle_cached_2.py example. The Circle class needs to have the self._diameter = None initialized. I’m getting an AttributeError: 'Circle' object has no attribute '_diameter' Exception if I access it first without mutating the radius.

>>> from circle_cached_2 import Circle
>>> circle = Circle(25)
>>> circle.radius
>>> circle.diameter
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "directory to file", line 21, in diameter
    if self._diameter is None:
AttributeError: 'Circle' object has no attribute '_diameter'
>>> circle.radius = 100
>>> circle.diameter
Avatar image for Darren Jones

Darren Jones RP Team on Jan. 5, 2024

Hi matheorism. I’ve just tried running the code that you’ve posted, using the circle_cached_2.py from the course materials, and it runs OK for me (i.e. the first call to circle_diameter works OK). I’d suggest that there’s an error in the version of circle_cached_2.py that you’re using, so it will probably pay to double-check what’s in the file against the course files - it’s easy to make a subtle typo that leads to issues like this!

Become a Member to join the conversation.