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.
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.
matheorism on Dec. 29, 2023
Hi Darren,
I’ve noticed on
circle_cached_2.py
example. TheCircle
class needs to have theself._diameter = None
initialized. I’m getting anAttributeError: 'Circle' object has no attribute '_diameter'
Exception if I access it first without mutating theradius
.