Django's Cached Property

COMPUTER PROGRAMMING

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.