Django's Cached Property
I've been trying to understand how django's decorator @cached_property works. Here is the class definition (original source):
# Got from django code base
class cached_property:
"""
Decorator that converts a method with a single self argument into a
property cached on the instance.
Optional ``name`` argument allows you to make cached properties of other
methods. (e.g. url = cached_property(get_absolute_url, name='url') )
"""
def __init__(self, func, name=None):
self.func = func
self.__doc__ = getattr(func, '__doc__')
self.name = name or func.__name__
def __get__(self, instance, cls=None):
"""
Call the function and put the return value in instance.__dict__ so that
subsequent attribute access on the instance returns the cached value
instead of calling cached_property.__get__().
"""
if instance is None:
return self
res = instance.__dict__[self.name] = self.func(instance)
return res
This will be used in a class as follows:
class Double():
def __init__(self, x):
self.x = x
@cached_property
def square(self):
return self.x * self.x
d = Double(2)
d.square # Prints 4. Performs computation
d.square # Prints 4. Uses the cached value
When the instance d of Double first accesses the square property, the __get__ from the cached_property class is called. This method receives the instance d as one of its parameters. It then calls the actual function and stores the result in the instance's __dict__[square] variable. The next time the square property is accessed, it picks up the appropriate value from its __dict__ and the function is not called. This is because d has a lookup chain starting from b.__dict__['square'] as explained here.
However should the object be changed, for example:
d.x = 3
d.square # Prints 4
the square still prints 4 instead of 9. This is because the square method is still accessing the cached value. To update this, the __dict__[square] parameter has to be cleared before.
d.x = 3
del d.__dict__['square']
d.square # Now prints 9
Clearing this will force a recomputation of the square variable. However, it's really easy to skip this, so I think the cached_property should be used where the objects parameters are not expected to change.